Revision 71a4c605
b/Makefile.am | ||
---|---|---|
727 | 727 |
src/Ganeti/Storage/Utils.hs \ |
728 | 728 |
src/Ganeti/THH.hs \ |
729 | 729 |
src/Ganeti/Types.hs \ |
730 |
src/Ganeti/UDSServer.hs \ |
|
730 | 731 |
src/Ganeti/Utils.hs |
731 | 732 |
|
732 | 733 |
HS_TEST_SRCS = \ |
b/src/Ganeti/Luxi.hs | ||
---|---|---|
6 | 6 |
|
7 | 7 |
{- |
8 | 8 |
|
9 |
Copyright (C) 2009, 2010, 2011, 2012 Google Inc. |
|
9 |
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
|
|
10 | 10 |
|
11 | 11 |
This program is free software; you can redistribute it and/or modify |
12 | 12 |
it under the terms of the GNU General Public License as published by |
... | ... | |
52 | 52 |
, allLuxiCalls |
53 | 53 |
) where |
54 | 54 |
|
55 |
import Control.Exception (catch) |
|
56 |
import Data.IORef |
|
57 |
import qualified Data.ByteString as B |
|
58 |
import qualified Data.ByteString.Lazy as BL |
|
59 |
import qualified Data.ByteString.UTF8 as UTF8 |
|
60 |
import qualified Data.ByteString.Lazy.UTF8 as UTF8L |
|
61 |
import Data.Word (Word8) |
|
62 | 55 |
import Control.Monad |
56 |
import qualified Data.ByteString.UTF8 as UTF8 |
|
63 | 57 |
import Text.JSON (encodeStrict, decodeStrict) |
64 | 58 |
import qualified Text.JSON as J |
65 | 59 |
import Text.JSON.Pretty (pp_value) |
66 | 60 |
import Text.JSON.Types |
67 |
import System.Directory (removeFile) |
|
68 |
import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..)) |
|
69 |
import System.IO.Error (isEOFError) |
|
70 |
import System.Timeout |
|
71 |
import qualified Network.Socket as S |
|
72 | 61 |
|
73 | 62 |
import Ganeti.BasicTypes |
74 | 63 |
import Ganeti.Constants |
75 | 64 |
import Ganeti.Errors |
76 | 65 |
import Ganeti.JSON |
66 |
import Ganeti.UDSServer |
|
77 | 67 |
import Ganeti.OpParams (pTagsObject) |
78 | 68 |
import Ganeti.OpCodes |
79 | 69 |
import qualified Ganeti.Query.Language as Qlang |
80 |
import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..)) |
|
81 | 70 |
import Ganeti.THH |
82 | 71 |
import Ganeti.Types |
83 |
import Ganeti.Utils |
|
84 |
|
|
85 |
-- * Utility functions |
|
86 | 72 |
|
87 |
-- | Wrapper over System.Timeout.timeout that fails in the IO monad. |
|
88 |
withTimeout :: Int -> String -> IO a -> IO a |
|
89 |
withTimeout secs descr action = do |
|
90 |
result <- timeout (secs * 1000000) action |
|
91 |
case result of |
|
92 |
Nothing -> fail $ "Timeout in " ++ descr |
|
93 |
Just v -> return v |
|
94 |
|
|
95 |
-- * Generic protocol functionality |
|
96 |
|
|
97 |
-- | Result of receiving a message from the socket. |
|
98 |
data RecvResult = RecvConnClosed -- ^ Connection closed |
|
99 |
| RecvError String -- ^ Any other error |
|
100 |
| RecvOk String -- ^ Successfull receive |
|
101 |
deriving (Show, Eq) |
|
102 | 73 |
|
103 | 74 |
-- | Currently supported Luxi operations and JSON serialization. |
104 | 75 |
$(genLuxiOp "LuxiOp" |
... | ... | |
199 | 170 |
-- | Type holding the initial (unparsed) Luxi call. |
200 | 171 |
data LuxiCall = LuxiCall LuxiReq JSValue |
201 | 172 |
|
202 |
-- | The end-of-message separator. |
|
203 |
eOM :: Word8 |
|
204 |
eOM = 3 |
|
205 |
|
|
206 |
-- | The end-of-message encoded as a ByteString. |
|
207 |
bEOM :: B.ByteString |
|
208 |
bEOM = B.singleton eOM |
|
209 |
|
|
210 |
-- | Valid keys in the requests and responses. |
|
211 |
data MsgKeys = Method |
|
212 |
| Args |
|
213 |
| Success |
|
214 |
| Result |
|
215 |
|
|
216 |
-- | The serialisation of MsgKeys into strings in messages. |
|
217 |
$(genStrOfKey ''MsgKeys "strOfKey") |
|
218 |
|
|
219 |
-- | Luxi client encapsulation. |
|
220 |
data Client = Client { socket :: Handle -- ^ The socket of the client |
|
221 |
, rbuf :: IORef B.ByteString -- ^ Already received buffer |
|
222 |
} |
|
223 |
|
|
224 |
-- | Connects to the master daemon and returns a luxi Client. |
|
225 |
getClient :: String -> IO Client |
|
226 |
getClient path = do |
|
227 |
s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
|
228 |
withTimeout luxiDefCtmo "creating luxi connection" $ |
|
229 |
S.connect s (S.SockAddrUnix path) |
|
230 |
rf <- newIORef B.empty |
|
231 |
h <- S.socketToHandle s ReadWriteMode |
|
232 |
return Client { socket=h, rbuf=rf } |
|
233 |
|
|
234 |
-- | Creates and returns a server endpoint. |
|
235 |
getServer :: Bool -> FilePath -> IO S.Socket |
|
236 |
getServer setOwner path = do |
|
237 |
s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
|
238 |
S.bindSocket s (S.SockAddrUnix path) |
|
239 |
when setOwner . setOwnerAndGroupFromNames path GanetiLuxid $ |
|
240 |
ExtraGroup DaemonsGroup |
|
241 |
S.listen s 5 -- 5 is the max backlog |
|
242 |
return s |
|
243 |
|
|
244 |
-- | Closes a server endpoint. |
|
245 |
-- FIXME: this should be encapsulated into a nicer type. |
|
246 |
closeServer :: FilePath -> S.Socket -> IO () |
|
247 |
closeServer path sock = do |
|
248 |
S.sClose sock |
|
249 |
removeFile path |
|
250 |
|
|
251 |
-- | Accepts a client |
|
252 |
acceptClient :: S.Socket -> IO Client |
|
253 |
acceptClient s = do |
|
254 |
-- second return is the address of the client, which we ignore here |
|
255 |
(client_socket, _) <- S.accept s |
|
256 |
new_buffer <- newIORef B.empty |
|
257 |
handle <- S.socketToHandle client_socket ReadWriteMode |
|
258 |
return Client { socket=handle, rbuf=new_buffer } |
|
259 |
|
|
260 |
-- | Closes the client socket. |
|
261 |
closeClient :: Client -> IO () |
|
262 |
closeClient = hClose . socket |
|
263 |
|
|
264 |
-- | Sends a message over a luxi transport. |
|
265 |
sendMsg :: Client -> String -> IO () |
|
266 |
sendMsg s buf = withTimeout luxiDefRwto "sending luxi message" $ do |
|
267 |
let encoded = UTF8L.fromString buf |
|
268 |
handle = socket s |
|
269 |
BL.hPut handle encoded |
|
270 |
B.hPut handle bEOM |
|
271 |
hFlush handle |
|
272 |
|
|
273 |
-- | Given a current buffer and the handle, it will read from the |
|
274 |
-- network until we get a full message, and it will return that |
|
275 |
-- message and the leftover buffer contents. |
|
276 |
recvUpdate :: Handle -> B.ByteString -> IO (B.ByteString, B.ByteString) |
|
277 |
recvUpdate handle obuf = do |
|
278 |
nbuf <- withTimeout luxiDefRwto "reading luxi response" $ do |
|
279 |
_ <- hWaitForInput handle (-1) |
|
280 |
B.hGetNonBlocking handle 4096 |
|
281 |
let (msg, remaining) = B.break (eOM ==) nbuf |
|
282 |
newbuf = B.append obuf msg |
|
283 |
if B.null remaining |
|
284 |
then recvUpdate handle newbuf |
|
285 |
else return (newbuf, B.tail remaining) |
|
286 |
|
|
287 |
-- | Waits for a message over a luxi transport. |
|
288 |
recvMsg :: Client -> IO String |
|
289 |
recvMsg s = do |
|
290 |
cbuf <- readIORef $ rbuf s |
|
291 |
let (imsg, ibuf) = B.break (eOM ==) cbuf |
|
292 |
(msg, nbuf) <- |
|
293 |
if B.null ibuf -- if old buffer didn't contain a full message |
|
294 |
then recvUpdate (socket s) cbuf -- then we read from network |
|
295 |
else return (imsg, B.tail ibuf) -- else we return data from our buffer |
|
296 |
writeIORef (rbuf s) nbuf |
|
297 |
return $ UTF8.toString msg |
|
298 |
|
|
299 |
-- | Extended wrapper over recvMsg. |
|
300 |
recvMsgExt :: Client -> IO RecvResult |
|
301 |
recvMsgExt s = |
|
302 |
Control.Exception.catch (liftM RecvOk (recvMsg s)) $ \e -> |
|
303 |
return $ if isEOFError e |
|
304 |
then RecvConnClosed |
|
305 |
else RecvError (show e) |
|
306 |
|
|
307 | 173 |
-- | Serialize a request to String. |
308 | 174 |
buildCall :: LuxiOp -- ^ The method |
309 | 175 |
-> String -- ^ The serialized form |
... | ... | |
314 | 180 |
jo = toJSObject ja |
315 | 181 |
in encodeStrict jo |
316 | 182 |
|
317 |
-- | Serialize the response to String. |
|
318 |
buildResponse :: Bool -- ^ Success |
|
319 |
-> JSValue -- ^ The arguments |
|
320 |
-> String -- ^ The serialized form |
|
321 |
buildResponse success args = |
|
322 |
let ja = [ (strOfKey Success, JSBool success) |
|
323 |
, (strOfKey Result, args)] |
|
324 |
jo = toJSObject ja |
|
325 |
in encodeStrict jo |
|
326 |
|
|
327 | 183 |
-- | Check that luxi request contains the required keys and parse it. |
328 | 184 |
validateCall :: String -> Result LuxiCall |
329 | 185 |
validateCall s = do |
b/src/Ganeti/UDSServer.hs | ||
---|---|---|
1 |
{-# LANGUAGE TemplateHaskell #-} |
|
2 |
|
|
3 |
{-| Implementation of the Ganeti Unix Domain Socket JSON server interface. |
|
4 |
|
|
5 |
-} |
|
6 |
|
|
7 |
{- |
|
8 |
|
|
9 |
Copyright (C) 2013 Google Inc. |
|
10 |
|
|
11 |
This program is free software; you can redistribute it and/or modify |
|
12 |
it under the terms of the GNU General Public License as published by |
|
13 |
the Free Software Foundation; either version 2 of the License, or |
|
14 |
(at your option) any later version. |
|
15 |
|
|
16 |
This program is distributed in the hope that it will be useful, but |
|
17 |
WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
19 |
General Public License for more details. |
|
20 |
|
|
21 |
You should have received a copy of the GNU General Public License |
|
22 |
along with this program; if not, write to the Free Software |
|
23 |
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
24 |
02110-1301, USA. |
|
25 |
|
|
26 |
-} |
|
27 |
|
|
28 |
module Ganeti.UDSServer |
|
29 |
( Client |
|
30 |
, RecvResult(..) |
|
31 |
, MsgKeys(..) |
|
32 |
, strOfKey |
|
33 |
, getClient |
|
34 |
, getServer |
|
35 |
, acceptClient |
|
36 |
, closeClient |
|
37 |
, closeServer |
|
38 |
, buildResponse |
|
39 |
, recvMsg |
|
40 |
, recvMsgExt |
|
41 |
, sendMsg |
|
42 |
) where |
|
43 |
|
|
44 |
import Control.Exception (catch) |
|
45 |
import Data.IORef |
|
46 |
import qualified Data.ByteString as B |
|
47 |
import qualified Data.ByteString.Lazy as BL |
|
48 |
import qualified Data.ByteString.UTF8 as UTF8 |
|
49 |
import qualified Data.ByteString.Lazy.UTF8 as UTF8L |
|
50 |
import Data.Word (Word8) |
|
51 |
import Control.Monad |
|
52 |
import qualified Network.Socket as S |
|
53 |
import System.Directory (removeFile) |
|
54 |
import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..)) |
|
55 |
import System.IO.Error (isEOFError) |
|
56 |
import System.Timeout |
|
57 |
import Text.JSON (encodeStrict) |
|
58 |
import Text.JSON.Types |
|
59 |
|
|
60 |
import Ganeti.Constants |
|
61 |
import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..)) |
|
62 |
import Ganeti.THH |
|
63 |
import Ganeti.Utils |
|
64 |
|
|
65 |
|
|
66 |
-- * Utility functions |
|
67 |
|
|
68 |
-- | Wrapper over System.Timeout.timeout that fails in the IO monad. |
|
69 |
withTimeout :: Int -> String -> IO a -> IO a |
|
70 |
withTimeout secs descr action = do |
|
71 |
result <- timeout (secs * 1000000) action |
|
72 |
case result of |
|
73 |
Nothing -> fail $ "Timeout in " ++ descr |
|
74 |
Just v -> return v |
|
75 |
|
|
76 |
|
|
77 |
-- * Generic protocol functionality |
|
78 |
|
|
79 |
-- | Result of receiving a message from the socket. |
|
80 |
data RecvResult = RecvConnClosed -- ^ Connection closed |
|
81 |
| RecvError String -- ^ Any other error |
|
82 |
| RecvOk String -- ^ Successfull receive |
|
83 |
deriving (Show, Eq) |
|
84 |
|
|
85 |
|
|
86 |
-- | The end-of-message separator. |
|
87 |
eOM :: Word8 |
|
88 |
eOM = 3 |
|
89 |
|
|
90 |
-- | The end-of-message encoded as a ByteString. |
|
91 |
bEOM :: B.ByteString |
|
92 |
bEOM = B.singleton eOM |
|
93 |
|
|
94 |
-- | Valid keys in the requests and responses. |
|
95 |
data MsgKeys = Method |
|
96 |
| Args |
|
97 |
| Success |
|
98 |
| Result |
|
99 |
|
|
100 |
-- | The serialisation of MsgKeys into strings in messages. |
|
101 |
$(genStrOfKey ''MsgKeys "strOfKey") |
|
102 |
|
|
103 |
|
|
104 |
-- | Luxi client encapsulation. |
|
105 |
data Client = Client { socket :: Handle -- ^ The socket of the client |
|
106 |
, rbuf :: IORef B.ByteString -- ^ Already received buffer |
|
107 |
} |
|
108 |
|
|
109 |
-- | Connects to the master daemon and returns a luxi Client. |
|
110 |
getClient :: String -> IO Client |
|
111 |
getClient path = do |
|
112 |
s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
|
113 |
withTimeout luxiDefCtmo "creating luxi connection" $ |
|
114 |
S.connect s (S.SockAddrUnix path) |
|
115 |
rf <- newIORef B.empty |
|
116 |
h <- S.socketToHandle s ReadWriteMode |
|
117 |
return Client { socket=h, rbuf=rf } |
|
118 |
|
|
119 |
-- | Creates and returns a server endpoint. |
|
120 |
getServer :: Bool -> FilePath -> IO S.Socket |
|
121 |
getServer setOwner path = do |
|
122 |
s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
|
123 |
S.bindSocket s (S.SockAddrUnix path) |
|
124 |
when setOwner . setOwnerAndGroupFromNames path GanetiLuxid $ |
|
125 |
ExtraGroup DaemonsGroup |
|
126 |
S.listen s 5 -- 5 is the max backlog |
|
127 |
return s |
|
128 |
|
|
129 |
-- | Closes a server endpoint. |
|
130 |
-- FIXME: this should be encapsulated into a nicer type. |
|
131 |
closeServer :: FilePath -> S.Socket -> IO () |
|
132 |
closeServer path sock = do |
|
133 |
S.sClose sock |
|
134 |
removeFile path |
|
135 |
|
|
136 |
-- | Accepts a client |
|
137 |
acceptClient :: S.Socket -> IO Client |
|
138 |
acceptClient s = do |
|
139 |
-- second return is the address of the client, which we ignore here |
|
140 |
(client_socket, _) <- S.accept s |
|
141 |
new_buffer <- newIORef B.empty |
|
142 |
handle <- S.socketToHandle client_socket ReadWriteMode |
|
143 |
return Client { socket=handle, rbuf=new_buffer } |
|
144 |
|
|
145 |
-- | Closes the client socket. |
|
146 |
closeClient :: Client -> IO () |
|
147 |
closeClient = hClose . socket |
|
148 |
|
|
149 |
-- | Sends a message over a luxi transport. |
|
150 |
sendMsg :: Client -> String -> IO () |
|
151 |
sendMsg s buf = withTimeout luxiDefRwto "sending luxi message" $ do |
|
152 |
let encoded = UTF8L.fromString buf |
|
153 |
handle = socket s |
|
154 |
BL.hPut handle encoded |
|
155 |
B.hPut handle bEOM |
|
156 |
hFlush handle |
|
157 |
|
|
158 |
-- | Given a current buffer and the handle, it will read from the |
|
159 |
-- network until we get a full message, and it will return that |
|
160 |
-- message and the leftover buffer contents. |
|
161 |
recvUpdate :: Handle -> B.ByteString -> IO (B.ByteString, B.ByteString) |
|
162 |
recvUpdate handle obuf = do |
|
163 |
nbuf <- withTimeout luxiDefRwto "reading luxi response" $ do |
|
164 |
_ <- hWaitForInput handle (-1) |
|
165 |
B.hGetNonBlocking handle 4096 |
|
166 |
let (msg, remaining) = B.break (eOM ==) nbuf |
|
167 |
newbuf = B.append obuf msg |
|
168 |
if B.null remaining |
|
169 |
then recvUpdate handle newbuf |
|
170 |
else return (newbuf, B.tail remaining) |
|
171 |
|
|
172 |
-- | Waits for a message over a luxi transport. |
|
173 |
recvMsg :: Client -> IO String |
|
174 |
recvMsg s = do |
|
175 |
cbuf <- readIORef $ rbuf s |
|
176 |
let (imsg, ibuf) = B.break (eOM ==) cbuf |
|
177 |
(msg, nbuf) <- |
|
178 |
if B.null ibuf -- if old buffer didn't contain a full message |
|
179 |
then recvUpdate (socket s) cbuf -- then we read from network |
|
180 |
else return (imsg, B.tail ibuf) -- else we return data from our buffer |
|
181 |
writeIORef (rbuf s) nbuf |
|
182 |
return $ UTF8.toString msg |
|
183 |
|
|
184 |
-- | Extended wrapper over recvMsg. |
|
185 |
recvMsgExt :: Client -> IO RecvResult |
|
186 |
recvMsgExt s = |
|
187 |
Control.Exception.catch (liftM RecvOk (recvMsg s)) $ \e -> |
|
188 |
return $ if isEOFError e |
|
189 |
then RecvConnClosed |
|
190 |
else RecvError (show e) |
|
191 |
|
|
192 |
|
|
193 |
-- | Serialize the response to String. |
|
194 |
buildResponse :: Bool -- ^ Success |
|
195 |
-> JSValue -- ^ The arguments |
|
196 |
-> String -- ^ The serialized form |
|
197 |
buildResponse success args = |
|
198 |
let ja = [ (strOfKey Success, JSBool success) |
|
199 |
, (strOfKey Result, args)] |
|
200 |
jo = toJSObject ja |
|
201 |
in encodeStrict jo |
Also available in: Unified diff