Statistics
| Branch: | Tag: | Revision:

root / Ganeti / Luxi.hs @ 9a2ff880

History | View | Annotate | Download (6.1 kB)

1
{-| Implementation of the Ganeti LUXI interface.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2009 Google Inc.
8

    
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.
13

    
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.
18

    
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
22
02110-1301, USA.
23

    
24
-}
25

    
26
module Ganeti.Luxi
27
    ( LuxiOp(..)
28
    , Client
29
    , getClient
30
    , closeClient
31
    , callMethod
32
    , submitManyJobs
33
    , queryJobsStatus
34
    ) where
35

    
36
import Data.List
37
import Data.IORef
38
import Control.Monad
39
import Text.JSON (JSObject, JSValue, toJSObject, encodeStrict, decodeStrict)
40
import qualified Text.JSON as J
41
import Text.JSON.Types
42
import System.Timeout
43
import qualified Network.Socket as S
44

    
45
import Ganeti.HTools.Utils
46
import Ganeti.HTools.Types
47

    
48
import Ganeti.Jobs (JobStatus)
49

    
50
-- * Utility functions
51

    
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
56
    (case result of
57
       Nothing -> fail $ "Timeout in " ++ descr
58
       Just v -> return v)
59

    
60
-- * Generic protocol functionality
61

    
62
-- | Currently supported Luxi operations.
63
data LuxiOp = QueryInstances
64
            | QueryNodes
65
            | QueryJobs
66
            | SubmitManyJobs
67

    
68
-- | The serialisation of LuxiOps into strings in messages.
69
strOfOp :: LuxiOp -> String
70
strOfOp QueryNodes = "QueryNodes"
71
strOfOp QueryInstances = "QueryInstances"
72
strOfOp QueryJobs = "QueryJobs"
73
strOfOp SubmitManyJobs = "SubmitManyJobs"
74

    
75
-- | The end-of-message separator.
76
eOM :: Char
77
eOM = '\3'
78

    
79
-- | Valid keys in the requests and responses.
80
data MsgKeys = Method
81
             | Args
82
             | Success
83
             | Result
84

    
85
-- | The serialisation of MsgKeys into strings in messages.
86
strOfKey :: MsgKeys -> String
87
strOfKey Method = "method"
88
strOfKey Args = "args"
89
strOfKey Success = "success"
90
strOfKey Result = "result"
91

    
92
-- | Luxi client encapsulation.
93
data Client = Client { socket :: S.Socket   -- ^ The socket of the client
94
                     , rbuf :: IORef String -- ^ Already received buffer
95
                     }
96

    
97
-- | Connects to the master daemon and returns a luxi Client.
98
getClient :: String -> IO Client
99
getClient path = do
100
    s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
101
    withTimeout connTimeout "creating luxi connection" $
102
                S.connect s (S.SockAddrUnix path)
103
    rf <- newIORef ""
104
    return Client { socket=s, rbuf=rf}
105

    
106
-- | Closes the client socket.
107
closeClient :: Client -> IO ()
108
closeClient = S.sClose . socket
109

    
110
-- | Sends a message over a luxi transport.
111
sendMsg :: Client -> String -> IO ()
112
sendMsg s buf =
113
    let _send obuf = do
114
          sbytes <- withTimeout queryTimeout
115
                    "sending luxi message" $
116
                    S.send (socket s) obuf
117
          (if sbytes == length obuf
118
           then return ()
119
           else _send (drop sbytes obuf))
120
    in _send (buf ++ [eOM])
121

    
122
-- | Waits for a message over a luxi transport.
123
recvMsg :: Client -> IO String
124
recvMsg s = do
125
  let _recv obuf = do
126
              nbuf <- withTimeout queryTimeout "reading luxi response" $
127
                      S.recv (socket s) 4096
128
              let (msg, remaining) = break ((==) eOM) (obuf ++ nbuf)
129
              (if null remaining
130
               then _recv msg
131
               else return (msg, tail remaining))
132
  cbuf <- readIORef $ rbuf s
133
  (msg, nbuf) <- _recv cbuf
134
  writeIORef (rbuf s) nbuf
135
  return msg
136

    
137
-- | Serialize a request to String.
138
buildCall :: LuxiOp  -- ^ The method
139
          -> JSValue -- ^ The arguments
140
          -> String  -- ^ The serialized form
141
buildCall msg args =
142
    let ja = [(strOfKey Method,
143
               JSString $ toJSString $ strOfOp msg::JSValue),
144
              (strOfKey Args,
145
               args::JSValue)
146
             ]
147
        jo = toJSObject ja
148
    in encodeStrict jo
149

    
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
  arr <- fromJResult $ decodeStrict s::Result (JSObject JSValue)
155
  status <- fromObj (strOfKey Success) arr::Result Bool
156
  let rkey = strOfKey Result
157
  (if status
158
   then fromObj rkey arr
159
   else fromObj rkey arr >>= fail)
160

    
161
-- | Generic luxi method call.
162
callMethod :: LuxiOp -> JSValue -> Client -> IO (Result JSValue)
163
callMethod method args s = do
164
  sendMsg s $ buildCall method args
165
  result <- recvMsg s
166
  let rval = validateResult result
167
  return rval
168

    
169
-- | Specialized submitManyJobs call.
170
submitManyJobs :: Client -> JSValue -> IO (Result [String])
171
submitManyJobs s jobs = do
172
  rval <- callMethod SubmitManyJobs jobs s
173
  -- map each result (status, payload) pair into a nice Result ADT
174
  return $ case rval of
175
             Bad x -> Bad x
176
             Ok (JSArray r) ->
177
                 mapM (\v -> case v of
178
                               JSArray [JSBool True, JSString x] ->
179
                                   Ok (fromJSString x)
180
                               JSArray [JSBool False, JSString x] ->
181
                                   Bad (fromJSString x)
182
                               _ -> Bad "Unknown result from the master daemon"
183
                      ) r
184
             x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
185

    
186
-- | Custom queryJobs call.
187
queryJobsStatus :: Client -> [String] -> IO (Result [JobStatus])
188
queryJobsStatus s jids = do
189
  rval <- callMethod QueryJobs (J.showJSON (jids, ["status"])) s
190
  return $ case rval of
191
             Bad x -> Bad x
192
             Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
193
                       J.Ok vals -> if any null vals
194
                                    then Bad "Missing job status field"
195
                                    else Ok (map head vals)
196
                       J.Error x -> Bad x