Statistics
| Branch: | Tag: | Revision:

root / Ganeti / Luxi.hs @ 1e3dccc8

History | View | Annotate | Download (6.1 kB)

1 6583e677 Iustin Pop
{-| Implementation of the Ganeti LUXI interface.
2 6583e677 Iustin Pop
3 6583e677 Iustin Pop
-}
4 6583e677 Iustin Pop
5 6583e677 Iustin Pop
{-
6 6583e677 Iustin Pop
7 6583e677 Iustin Pop
Copyright (C) 2009 Google Inc.
8 6583e677 Iustin Pop
9 6583e677 Iustin Pop
This program is free software; you can redistribute it and/or modify
10 6583e677 Iustin Pop
it under the terms of the GNU General Public License as published by
11 6583e677 Iustin Pop
the Free Software Foundation; either version 2 of the License, or
12 6583e677 Iustin Pop
(at your option) any later version.
13 6583e677 Iustin Pop
14 6583e677 Iustin Pop
This program is distributed in the hope that it will be useful, but
15 6583e677 Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
16 6583e677 Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 6583e677 Iustin Pop
General Public License for more details.
18 6583e677 Iustin Pop
19 6583e677 Iustin Pop
You should have received a copy of the GNU General Public License
20 6583e677 Iustin Pop
along with this program; if not, write to the Free Software
21 6583e677 Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 6583e677 Iustin Pop
02110-1301, USA.
23 6583e677 Iustin Pop
24 6583e677 Iustin Pop
-}
25 6583e677 Iustin Pop
26 6583e677 Iustin Pop
module Ganeti.Luxi
27 6583e677 Iustin Pop
    ( LuxiOp(..)
28 6583e677 Iustin Pop
    , Client
29 6583e677 Iustin Pop
    , getClient
30 6583e677 Iustin Pop
    , closeClient
31 6583e677 Iustin Pop
    , callMethod
32 9a2ff880 Iustin Pop
    , submitManyJobs
33 9a2ff880 Iustin Pop
    , queryJobsStatus
34 6583e677 Iustin Pop
    ) where
35 6583e677 Iustin Pop
36 6583e677 Iustin Pop
import Data.IORef
37 6583e677 Iustin Pop
import Control.Monad
38 0903280b Iustin Pop
import Text.JSON (encodeStrict, decodeStrict)
39 6583e677 Iustin Pop
import qualified Text.JSON as J
40 6583e677 Iustin Pop
import Text.JSON.Types
41 6583e677 Iustin Pop
import System.Timeout
42 6583e677 Iustin Pop
import qualified Network.Socket as S
43 6583e677 Iustin Pop
44 6583e677 Iustin Pop
import Ganeti.HTools.Utils
45 6583e677 Iustin Pop
import Ganeti.HTools.Types
46 6583e677 Iustin Pop
47 9a2ff880 Iustin Pop
import Ganeti.Jobs (JobStatus)
48 9a2ff880 Iustin Pop
49 6583e677 Iustin Pop
-- * Utility functions
50 6583e677 Iustin Pop
51 6583e677 Iustin Pop
-- | Wrapper over System.Timeout.timeout that fails in the IO monad.
52 6583e677 Iustin Pop
withTimeout :: Int -> String -> IO a -> IO a
53 6583e677 Iustin Pop
withTimeout secs descr action = do
54 6583e677 Iustin Pop
    result <- timeout (secs * 1000000) action
55 6583e677 Iustin Pop
    (case result of
56 6583e677 Iustin Pop
       Nothing -> fail $ "Timeout in " ++ descr
57 6583e677 Iustin Pop
       Just v -> return v)
58 6583e677 Iustin Pop
59 6583e677 Iustin Pop
-- * Generic protocol functionality
60 6583e677 Iustin Pop
61 6583e677 Iustin Pop
-- | Currently supported Luxi operations.
62 6583e677 Iustin Pop
data LuxiOp = QueryInstances
63 6583e677 Iustin Pop
            | QueryNodes
64 a1b5eeaf Iustin Pop
            | QueryJobs
65 f89235f1 Iustin Pop
            | QueryClusterInfo
66 a1b5eeaf Iustin Pop
            | SubmitManyJobs
67 6583e677 Iustin Pop
68 6583e677 Iustin Pop
-- | The serialisation of LuxiOps into strings in messages.
69 6583e677 Iustin Pop
strOfOp :: LuxiOp -> String
70 6583e677 Iustin Pop
strOfOp QueryNodes = "QueryNodes"
71 6583e677 Iustin Pop
strOfOp QueryInstances = "QueryInstances"
72 a1b5eeaf Iustin Pop
strOfOp QueryJobs = "QueryJobs"
73 f89235f1 Iustin Pop
strOfOp QueryClusterInfo = "QueryClusterInfo"
74 a1b5eeaf Iustin Pop
strOfOp SubmitManyJobs = "SubmitManyJobs"
75 6583e677 Iustin Pop
76 6583e677 Iustin Pop
-- | The end-of-message separator.
77 6583e677 Iustin Pop
eOM :: Char
78 6583e677 Iustin Pop
eOM = '\3'
79 6583e677 Iustin Pop
80 6583e677 Iustin Pop
-- | Valid keys in the requests and responses.
81 6583e677 Iustin Pop
data MsgKeys = Method
82 6583e677 Iustin Pop
             | Args
83 6583e677 Iustin Pop
             | Success
84 6583e677 Iustin Pop
             | Result
85 6583e677 Iustin Pop
86 6583e677 Iustin Pop
-- | The serialisation of MsgKeys into strings in messages.
87 6583e677 Iustin Pop
strOfKey :: MsgKeys -> String
88 6583e677 Iustin Pop
strOfKey Method = "method"
89 6583e677 Iustin Pop
strOfKey Args = "args"
90 6583e677 Iustin Pop
strOfKey Success = "success"
91 6583e677 Iustin Pop
strOfKey Result = "result"
92 6583e677 Iustin Pop
93 6583e677 Iustin Pop
-- | Luxi client encapsulation.
94 6583e677 Iustin Pop
data Client = Client { socket :: S.Socket   -- ^ The socket of the client
95 6583e677 Iustin Pop
                     , rbuf :: IORef String -- ^ Already received buffer
96 6583e677 Iustin Pop
                     }
97 6583e677 Iustin Pop
98 6583e677 Iustin Pop
-- | Connects to the master daemon and returns a luxi Client.
99 6583e677 Iustin Pop
getClient :: String -> IO Client
100 6583e677 Iustin Pop
getClient path = do
101 6583e677 Iustin Pop
    s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
102 6583e677 Iustin Pop
    withTimeout connTimeout "creating luxi connection" $
103 6583e677 Iustin Pop
                S.connect s (S.SockAddrUnix path)
104 6583e677 Iustin Pop
    rf <- newIORef ""
105 6583e677 Iustin Pop
    return Client { socket=s, rbuf=rf}
106 6583e677 Iustin Pop
107 6583e677 Iustin Pop
-- | Closes the client socket.
108 6583e677 Iustin Pop
closeClient :: Client -> IO ()
109 6583e677 Iustin Pop
closeClient = S.sClose . socket
110 6583e677 Iustin Pop
111 6583e677 Iustin Pop
-- | Sends a message over a luxi transport.
112 6583e677 Iustin Pop
sendMsg :: Client -> String -> IO ()
113 6583e677 Iustin Pop
sendMsg s buf =
114 6583e677 Iustin Pop
    let _send obuf = do
115 6583e677 Iustin Pop
          sbytes <- withTimeout queryTimeout
116 6583e677 Iustin Pop
                    "sending luxi message" $
117 6583e677 Iustin Pop
                    S.send (socket s) obuf
118 3a3c1eb4 Iustin Pop
          unless (sbytes == length obuf) $ _send (drop sbytes obuf)
119 6583e677 Iustin Pop
    in _send (buf ++ [eOM])
120 6583e677 Iustin Pop
121 6583e677 Iustin Pop
-- | Waits for a message over a luxi transport.
122 6583e677 Iustin Pop
recvMsg :: Client -> IO String
123 6583e677 Iustin Pop
recvMsg s = do
124 6583e677 Iustin Pop
  let _recv obuf = do
125 6583e677 Iustin Pop
              nbuf <- withTimeout queryTimeout "reading luxi response" $
126 6583e677 Iustin Pop
                      S.recv (socket s) 4096
127 5182e970 Iustin Pop
              let (msg, remaining) = break (eOM ==) (obuf ++ nbuf)
128 6583e677 Iustin Pop
              (if null remaining
129 6583e677 Iustin Pop
               then _recv msg
130 6583e677 Iustin Pop
               else return (msg, tail remaining))
131 6583e677 Iustin Pop
  cbuf <- readIORef $ rbuf s
132 6583e677 Iustin Pop
  (msg, nbuf) <- _recv cbuf
133 6583e677 Iustin Pop
  writeIORef (rbuf s) nbuf
134 6583e677 Iustin Pop
  return msg
135 6583e677 Iustin Pop
136 6583e677 Iustin Pop
-- | Serialize a request to String.
137 6583e677 Iustin Pop
buildCall :: LuxiOp  -- ^ The method
138 6583e677 Iustin Pop
          -> JSValue -- ^ The arguments
139 6583e677 Iustin Pop
          -> String  -- ^ The serialized form
140 6583e677 Iustin Pop
buildCall msg args =
141 6583e677 Iustin Pop
    let ja = [(strOfKey Method,
142 6583e677 Iustin Pop
               JSString $ toJSString $ strOfOp msg::JSValue),
143 6583e677 Iustin Pop
              (strOfKey Args,
144 6583e677 Iustin Pop
               args::JSValue)
145 6583e677 Iustin Pop
             ]
146 6583e677 Iustin Pop
        jo = toJSObject ja
147 6583e677 Iustin Pop
    in encodeStrict jo
148 6583e677 Iustin Pop
149 6583e677 Iustin Pop
-- | Check that luxi responses contain the required keys and that the
150 6583e677 Iustin Pop
-- call was successful.
151 6583e677 Iustin Pop
validateResult :: String -> Result JSValue
152 6583e677 Iustin Pop
validateResult s = do
153 262f3e6c Iustin Pop
  oarr <- fromJResult $ decodeStrict s::Result (JSObject JSValue)
154 262f3e6c Iustin Pop
  let arr = J.fromJSObject oarr
155 6583e677 Iustin Pop
  status <- fromObj (strOfKey Success) arr::Result Bool
156 6583e677 Iustin Pop
  let rkey = strOfKey Result
157 6583e677 Iustin Pop
  (if status
158 6583e677 Iustin Pop
   then fromObj rkey arr
159 6583e677 Iustin Pop
   else fromObj rkey arr >>= fail)
160 6583e677 Iustin Pop
161 6583e677 Iustin Pop
-- | Generic luxi method call.
162 6583e677 Iustin Pop
callMethod :: LuxiOp -> JSValue -> Client -> IO (Result JSValue)
163 6583e677 Iustin Pop
callMethod method args s = do
164 6583e677 Iustin Pop
  sendMsg s $ buildCall method args
165 6583e677 Iustin Pop
  result <- recvMsg s
166 6583e677 Iustin Pop
  let rval = validateResult result
167 6583e677 Iustin Pop
  return rval
168 9a2ff880 Iustin Pop
169 9a2ff880 Iustin Pop
-- | Specialized submitManyJobs call.
170 9a2ff880 Iustin Pop
submitManyJobs :: Client -> JSValue -> IO (Result [String])
171 9a2ff880 Iustin Pop
submitManyJobs s jobs = do
172 9a2ff880 Iustin Pop
  rval <- callMethod SubmitManyJobs jobs s
173 9a2ff880 Iustin Pop
  -- map each result (status, payload) pair into a nice Result ADT
174 9a2ff880 Iustin Pop
  return $ case rval of
175 9a2ff880 Iustin Pop
             Bad x -> Bad x
176 9a2ff880 Iustin Pop
             Ok (JSArray r) ->
177 9a2ff880 Iustin Pop
                 mapM (\v -> case v of
178 9a2ff880 Iustin Pop
                               JSArray [JSBool True, JSString x] ->
179 9a2ff880 Iustin Pop
                                   Ok (fromJSString x)
180 9a2ff880 Iustin Pop
                               JSArray [JSBool False, JSString x] ->
181 9a2ff880 Iustin Pop
                                   Bad (fromJSString x)
182 9a2ff880 Iustin Pop
                               _ -> Bad "Unknown result from the master daemon"
183 9a2ff880 Iustin Pop
                      ) r
184 9a2ff880 Iustin Pop
             x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
185 9a2ff880 Iustin Pop
186 9a2ff880 Iustin Pop
-- | Custom queryJobs call.
187 9a2ff880 Iustin Pop
queryJobsStatus :: Client -> [String] -> IO (Result [JobStatus])
188 9a2ff880 Iustin Pop
queryJobsStatus s jids = do
189 9a2ff880 Iustin Pop
  rval <- callMethod QueryJobs (J.showJSON (jids, ["status"])) s
190 9a2ff880 Iustin Pop
  return $ case rval of
191 9a2ff880 Iustin Pop
             Bad x -> Bad x
192 9a2ff880 Iustin Pop
             Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
193 9a2ff880 Iustin Pop
                       J.Ok vals -> if any null vals
194 9a2ff880 Iustin Pop
                                    then Bad "Missing job status field"
195 9a2ff880 Iustin Pop
                                    else Ok (map head vals)
196 9a2ff880 Iustin Pop
                       J.Error x -> Bad x