1 {-| Implementation of the LUXI client 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
26 module Ganeti.HTools.Luxi
33 import qualified Control.Exception as E
35 import Text.JSON (JSObject, JSValue, toJSObject, encodeStrict
36 , decodeStrict, readJSON, JSON)
37 import qualified Text.JSON as J
38 import Text.JSON.Types
40 import qualified Network.Socket as S
42 import Ganeti.HTools.Utils
43 import Ganeti.HTools.Loader
44 import Ganeti.HTools.Types
45 import qualified Ganeti.HTools.Node as Node
46 import qualified Ganeti.HTools.Instance as Instance
48 -- * Utility functions
50 -- | Small wrapper over readJSON.
51 fromJVal :: (Monad m, JSON a) => JSValue -> m a
54 J.Error s -> fail ("Cannot convert value " ++ show v ++ ", error: " ++ s)
57 -- | Ensure a given JSValue is actually a JSArray.
58 toArray :: (Monad m) => JSValue -> m [JSValue]
61 JSArray arr -> return arr
62 o -> fail ("Invalid input, expected array but got " ++ show o)
64 -- | Wrapper over System.Timeout.timeout that fails in the IO monad.
65 withTimeout :: Int -> String -> IO a -> IO a
66 withTimeout secs descr action = do
67 result <- timeout (secs * 1000000) action
69 Nothing -> fail $ "Timeout in " ++ descr
72 -- * Generic protocol functionality
74 -- | Currently supported Luxi operations.
75 data LuxiOp = QueryInstances
78 -- | The serialisation of LuxiOps into strings in messages.
79 strOfOp :: LuxiOp -> String
80 strOfOp QueryNodes = "QueryNodes"
81 strOfOp QueryInstances = "QueryInstances"
83 -- | The end-of-message separator.
87 -- | Valid keys in the requests and responses.
93 -- | The serialisation of MsgKeys into strings in messages.
94 strOfKey :: MsgKeys -> String
95 strOfKey Method = "method"
96 strOfKey Args = "args"
97 strOfKey Success = "success"
98 strOfKey Result = "result"
100 -- | Luxi client encapsulation.
101 data Client = Client { socket :: S.Socket -- ^ The socket of the client
102 , rbuf :: IORef String -- ^ Already received buffer
105 -- | Connects to the master daemon and returns a luxi Client.
106 getClient :: String -> IO Client
108 s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
109 withTimeout connTimeout "creating luxi connection" $
110 S.connect s (S.SockAddrUnix path)
112 return Client { socket=s, rbuf=rf}
114 -- | Closes the client socket.
115 closeClient :: Client -> IO ()
116 closeClient = S.sClose . socket
118 -- | Sends a message over a luxi transport.
119 sendMsg :: Client -> String -> IO ()
122 sbytes <- withTimeout queryTimeout
123 "sending luxi message" $
124 S.send (socket s) obuf
125 (if sbytes == length obuf
127 else _send (drop sbytes obuf))
128 in _send (buf ++ [eOM])
130 -- | Waits for a message over a luxi transport.
131 recvMsg :: Client -> IO String
134 nbuf <- withTimeout queryTimeout "reading luxi response" $
135 S.recv (socket s) 4096
136 let (msg, rbuf) = break ((==) eOM) (obuf ++ nbuf)
139 else return (msg, drop 1 rbuf))
140 cbuf <- readIORef $ rbuf s
141 (msg, nbuf) <- _recv cbuf
142 writeIORef (rbuf s) nbuf
145 -- | Serialize a request to String.
146 buildCall :: LuxiOp -- ^ The method
147 -> JSValue -- ^ The arguments
148 -> String -- ^ The serialized form
150 let ja = [(strOfKey Method,
151 JSString $ toJSString $ strOfOp msg::JSValue),
158 -- | Check that luxi responses contain the required keys and that the
159 -- call was successful.
160 validateResult :: String -> Result JSValue
161 validateResult s = do
162 arr <- fromJResult $ decodeStrict s::Result (JSObject JSValue)
163 status <- fromObj (strOfKey Success) arr::Result Bool
164 let rkey = strOfKey Result
166 then fromObj rkey arr
167 else fromObj rkey arr >>= fail)
169 -- | Generic luxi method call.
170 callMethod :: LuxiOp -> JSValue -> Client -> IO (Result JSValue)
171 callMethod method args s = do
172 sendMsg s $ buildCall method args
174 let rval = validateResult result
177 -- * Data querying functionality
179 -- | The input data for node query.
180 queryNodesMsg :: JSValue
182 let nnames = JSArray []
184 "mtotal", "mnode", "mfree",
187 "offline", "drained"]
188 fields = JSArray $ map (JSString . toJSString) fnames
189 use_locking = JSBool False
190 in JSArray [nnames, fields, use_locking]
192 -- | The input data for instance query.
193 queryInstancesMsg :: JSValue
195 let nnames = JSArray []
197 "disk_usage", "be/memory", "be/vcpus",
198 "status", "pnode", "snodes"]
199 fields = JSArray $ map (JSString . toJSString) fnames
200 use_locking = JSBool False
201 in JSArray [nnames, fields, use_locking]
204 -- | Wraper over callMethod doing node query.
205 queryNodes :: Client -> IO (Result JSValue)
206 queryNodes = callMethod QueryNodes queryNodesMsg
208 -- | Wraper over callMethod doing instance query.
209 queryInstances :: Client -> IO (Result JSValue)
210 queryInstances = callMethod QueryInstances queryInstancesMsg
212 -- | Parse a instance list in JSON format.
213 getInstances :: NameAssoc
215 -> Result [(String, Instance.Instance)]
216 getInstances ktn arr = toArray arr >>= mapM (parseInstance ktn)
218 -- | Construct an instance from a JSON object.
219 parseInstance :: [(String, Ndx)]
221 -> Result (String, Instance.Instance)
222 parseInstance ktn (JSArray (name:disk:mem:vcpus:status:pnode:snodes:[])) = do
223 xname <- fromJVal name
224 xdisk <- fromJVal disk
226 xvcpus <- fromJVal vcpus
227 xpnode <- fromJVal pnode >>= lookupNode ktn xname
228 xsnodes <- fromJVal snodes::Result [JSString]
229 snode <- (if null xsnodes then return Node.noSecondary
230 else lookupNode ktn xname (fromJSString $ head xsnodes))
231 xrunning <- fromJVal status
232 let inst = Instance.create xname xmem xdisk xvcpus xrunning xpnode snode
235 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
237 -- | Parse a node list in JSON format.
238 getNodes :: JSValue -> Result [(String, Node.Node)]
239 getNodes arr = toArray arr >>= mapM parseNode
241 -- | Construct a node from a JSON object.
242 parseNode :: JSValue -> Result (String, Node.Node)
244 (name:mtotal:mnode:mfree:dtotal:dfree:ctotal:offline:drained:[]))
246 xname <- fromJVal name
247 xoffline <- fromJVal offline
249 then return $ Node.create xname 0 0 0 0 0 0 True
251 xdrained <- fromJVal drained
252 xmtotal <- fromJVal mtotal
253 xmnode <- fromJVal mnode
254 xmfree <- fromJVal mfree
255 xdtotal <- fromJVal dtotal
256 xdfree <- fromJVal dfree
257 xctotal <- fromJVal ctotal
258 return $ Node.create xname xmtotal xmnode xmfree
259 xdtotal xdfree xctotal (xoffline || xdrained))
262 parseNode v = fail ("Invalid node query result: " ++ show v)
264 -- * Main loader functionality
266 -- | Builds the cluster data from an URL.
267 loadData :: String -- ^ Unix socket to use as source
268 -> IO (Result (Node.AssocList, Instance.AssocList))
274 nodes <- queryNodes s
275 instances <- queryInstances s
276 return $ do -- Result monad
277 node_data <- nodes >>= getNodes
278 let (node_names, node_idx) = assignIndices node_data
279 inst_data <- instances >>= getInstances node_names
280 let (_, inst_idx) = assignIndices inst_data
281 return (node_idx, inst_idx)