Revision 62377cf5
b/Makefile.am | ||
---|---|---|
404 | 404 |
htools/Ganeti/BasicTypes.hs \ |
405 | 405 |
htools/Ganeti/Confd.hs \ |
406 | 406 |
htools/Ganeti/Confd/Server.hs \ |
407 |
htools/Ganeti/Confd/Utils.hs \ |
|
407 | 408 |
htools/Ganeti/Config.hs \ |
408 | 409 |
htools/Ganeti/Daemon.hs \ |
409 | 410 |
htools/Ganeti/Hash.hs \ |
b/htools/Ganeti/Confd/Server.hs | ||
---|---|---|
32 | 32 |
import Control.Concurrent |
33 | 33 |
import Control.Exception |
34 | 34 |
import Control.Monad (forever, liftM, when) |
35 |
import qualified Data.ByteString as B |
|
36 | 35 |
import Data.IORef |
37 | 36 |
import Data.List |
38 | 37 |
import qualified Data.Map as M |
... | ... | |
51 | 50 |
import Ganeti.HTools.Utils |
52 | 51 |
import Ganeti.Objects |
53 | 52 |
import Ganeti.Confd |
53 |
import Ganeti.Confd.Utils |
|
54 | 54 |
import Ganeti.Config |
55 | 55 |
import Ganeti.Hash |
56 | 56 |
import Ganeti.Logging |
... | ... | |
131 | 131 |
|
132 | 132 |
-- * Confd base functionality |
133 | 133 |
|
134 |
-- | Returns the HMAC key. |
|
135 |
getClusterHmac :: IO HashKey |
|
136 |
getClusterHmac = fmap B.unpack $ B.readFile C.confdHmacKey |
|
137 |
|
|
138 | 134 |
-- | Computes the node role. |
139 | 135 |
nodeRole :: ConfigData -> String -> Result ConfdNodeRole |
140 | 136 |
nodeRole cfg name = |
... | ... | |
242 | 238 |
(a, b, c, d, e, f) <- minors] |
243 | 239 |
return (ReplyStatusOk, J.showJSON encoded) |
244 | 240 |
|
245 |
-- | Parses a signed request. |
|
246 |
parseRequest :: HashKey -> String -> Result (String, String, ConfdRequest) |
|
247 |
parseRequest key str = do |
|
248 |
(SignedMessage hmac msg salt) <- fromJResult "parsing request" $ J.decode str |
|
249 |
req <- if verifyMac key (Just salt) msg hmac |
|
250 |
then fromJResult "parsing message" $ J.decode msg |
|
251 |
else Bad "HMAC verification failed" |
|
252 |
return (salt, msg, req) |
|
253 |
|
|
254 | 241 |
-- | Creates a ConfdReply from a given answer. |
255 | 242 |
serializeResponse :: Result StatusAnswer -> ConfdReply |
256 | 243 |
serializeResponse r = |
... | ... | |
262 | 249 |
, confdReplyAnswer = result |
263 | 250 |
, confdReplySerial = 0 } |
264 | 251 |
|
265 |
-- | Signs a message with a given key and salt. |
|
266 |
signMessage :: HashKey -> String -> String -> SignedMessage |
|
267 |
signMessage key salt msg = |
|
268 |
SignedMessage { signedMsgMsg = msg |
|
269 |
, signedMsgSalt = salt |
|
270 |
, signedMsgHmac = hmac |
|
271 |
} |
|
272 |
where hmac = computeMac key (Just salt) msg |
|
273 |
|
|
274 | 252 |
-- * Configuration handling |
275 | 253 |
|
276 | 254 |
-- ** Helper functions |
... | ... | |
483 | 461 |
Bad err -> logInfo $ "Failed to parse incoming message: " ++ err |
484 | 462 |
return () |
485 | 463 |
|
486 |
-- | Mesage parsing. This can either result in a good, valid message, |
|
487 |
-- or fail in the Result monad. |
|
488 |
parseMessage :: HashKey -> String -> Integer |
|
489 |
-> Result (String, ConfdRequest) |
|
490 |
parseMessage hmac msg curtime = do |
|
491 |
(salt, origmsg, request) <- parseRequest hmac msg |
|
492 |
ts <- tryRead "Parsing timestamp" salt::Result Integer |
|
493 |
if (abs (ts - curtime) > (fromIntegral C.confdMaxClockSkew)) |
|
494 |
then fail "Too old/too new timestamp or clock skew" |
|
495 |
else return (origmsg, request) |
|
496 |
|
|
497 | 464 |
-- | Inner helper function for a given client. This generates the |
498 | 465 |
-- final encoded message (as a string), ready to be sent out to the |
499 | 466 |
-- client. |
b/htools/Ganeti/Confd/Utils.hs | ||
---|---|---|
1 |
{-| Implementation of the Ganeti confd utilities. |
|
2 |
|
|
3 |
This holds a few utility functions that could be useful in both |
|
4 |
clients and servers. |
|
5 |
|
|
6 |
-} |
|
7 |
|
|
8 |
{- |
|
9 |
|
|
10 |
Copyright (C) 2011, 2012 Google Inc. |
|
11 |
|
|
12 |
This program is free software; you can redistribute it and/or modify |
|
13 |
it under the terms of the GNU General Public License as published by |
|
14 |
the Free Software Foundation; either version 2 of the License, or |
|
15 |
(at your option) any later version. |
|
16 |
|
|
17 |
This program is distributed in the hope that it will be useful, but |
|
18 |
WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
20 |
General Public License for more details. |
|
21 |
|
|
22 |
You should have received a copy of the GNU General Public License |
|
23 |
along with this program; if not, write to the Free Software |
|
24 |
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
25 |
02110-1301, USA. |
|
26 |
|
|
27 |
-} |
|
28 |
|
|
29 |
module Ganeti.Confd.Utils |
|
30 |
( getClusterHmac |
|
31 |
, parseRequest |
|
32 |
, parseMessage |
|
33 |
, signMessage |
|
34 |
) where |
|
35 |
|
|
36 |
import qualified Data.ByteString as B |
|
37 |
import qualified Text.JSON as J |
|
38 |
|
|
39 |
import Ganeti.BasicTypes |
|
40 |
import Ganeti.Confd |
|
41 |
import Ganeti.Hash |
|
42 |
import qualified Ganeti.Constants as C |
|
43 |
import Ganeti.HTools.JSON |
|
44 |
import Ganeti.HTools.Utils |
|
45 |
|
|
46 |
-- | Returns the HMAC key. |
|
47 |
getClusterHmac :: IO HashKey |
|
48 |
getClusterHmac = fmap B.unpack $ B.readFile C.confdHmacKey |
|
49 |
|
|
50 |
-- | Parses a signed request. |
|
51 |
parseRequest :: HashKey -> String -> Result (String, String, ConfdRequest) |
|
52 |
parseRequest key str = do |
|
53 |
(SignedMessage hmac msg salt) <- fromJResult "parsing request" $ J.decode str |
|
54 |
req <- if verifyMac key (Just salt) msg hmac |
|
55 |
then fromJResult "parsing message" $ J.decode msg |
|
56 |
else Bad "HMAC verification failed" |
|
57 |
return (salt, msg, req) |
|
58 |
|
|
59 |
-- | Mesage parsing. This can either result in a good, valid message, |
|
60 |
-- or fail in the Result monad. |
|
61 |
parseMessage :: HashKey -> String -> Integer |
|
62 |
-> Result (String, ConfdRequest) |
|
63 |
parseMessage hmac msg curtime = do |
|
64 |
(salt, origmsg, request) <- parseRequest hmac msg |
|
65 |
ts <- tryRead "Parsing timestamp" salt::Result Integer |
|
66 |
if (abs (ts - curtime) > (fromIntegral C.confdMaxClockSkew)) |
|
67 |
then fail "Too old/too new timestamp or clock skew" |
|
68 |
else return (origmsg, request) |
|
69 |
|
|
70 |
-- | Signs a message with a given key and salt. |
|
71 |
signMessage :: HashKey -> String -> String -> SignedMessage |
|
72 |
signMessage key salt msg = |
|
73 |
SignedMessage { signedMsgMsg = msg |
|
74 |
, signedMsgSalt = salt |
|
75 |
, signedMsgHmac = hmac |
|
76 |
} |
|
77 |
where hmac = computeMac key (Just salt) msg |
b/htools/Ganeti/HTools/Luxi.hs | ||
---|---|---|
33 | 33 |
import qualified Text.JSON |
34 | 34 |
|
35 | 35 |
import qualified Ganeti.Luxi as L |
36 |
import qualified Ganeti.Qlang as Qlang |
|
36 | 37 |
import Ganeti.HTools.Loader |
37 | 38 |
import Ganeti.HTools.Types |
38 | 39 |
import qualified Ganeti.HTools.Group as Group |
39 | 40 |
import qualified Ganeti.HTools.Node as Node |
40 | 41 |
import qualified Ganeti.HTools.Instance as Instance |
41 | 42 |
import Ganeti.HTools.JSON |
42 |
import Ganeti.Qlang as Qlang |
|
43 | 43 |
|
44 | 44 |
{-# ANN module "HLint: ignore Eta reduce" #-} |
45 | 45 |
|
... | ... | |
78 | 78 |
fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a |
79 | 79 |
fromJValWithStatus (st, v) = do |
80 | 80 |
st' <- fromJVal st |
81 |
L.checkRS st' v >>= fromJVal
|
|
81 |
Qlang.checkRS st' v >>= fromJVal
|
|
82 | 82 |
|
83 | 83 |
-- | Annotate errors when converting values with owner/attribute for |
84 | 84 |
-- better debugging. |
b/htools/Ganeti/HTools/QC.hs | ||
---|---|---|
75 | 75 |
import System.Process (readProcessWithExitCode) |
76 | 76 |
|
77 | 77 |
import qualified Ganeti.Confd as Confd |
78 |
import qualified Ganeti.Confd.Server as Confd.Server |
|
79 |
import qualified Ganeti.Confd.Utils as Confd.Utils |
|
78 | 80 |
import qualified Ganeti.Config as Config |
79 | 81 |
import qualified Ganeti.Daemon as Daemon |
80 | 82 |
import qualified Ganeti.Hash as Hash |
b/htools/Ganeti/Luxi.hs | ||
---|---|---|
27 | 27 |
|
28 | 28 |
module Ganeti.Luxi |
29 | 29 |
( LuxiOp(..) |
30 |
, ResultStatus(..) |
|
31 | 30 |
, LuxiReq(..) |
32 | 31 |
, Client |
33 | 32 |
, JobId |
34 | 33 |
, RecvResult(..) |
35 | 34 |
, TagObject(..) |
36 | 35 |
, strOfOp |
37 |
, checkRS |
|
38 | 36 |
, getClient |
39 | 37 |
, getServer |
40 | 38 |
, acceptClient |
... | ... | |
183 | 181 |
-- | The serialisation of LuxiOps into strings in messages. |
184 | 182 |
$(genStrOfOp ''LuxiOp "strOfOp") |
185 | 183 |
|
186 |
$(declareIADT "ResultStatus" |
|
187 |
[ ("RSNormal", 'rsNormal) |
|
188 |
, ("RSUnknown", 'rsUnknown) |
|
189 |
, ("RSNoData", 'rsNodata) |
|
190 |
, ("RSUnavailable", 'rsUnavail) |
|
191 |
, ("RSOffline", 'rsOffline) |
|
192 |
]) |
|
193 |
|
|
194 |
$(makeJSONInstance ''ResultStatus) |
|
195 |
|
|
196 | 184 |
-- | Type holding the initial (unparsed) Luxi call. |
197 | 185 |
data LuxiCall = LuxiCall LuxiReq JSValue |
198 | 186 |
|
199 |
-- | Check that ResultStatus is success or fail with descriptive message. |
|
200 |
checkRS :: (Monad m) => ResultStatus -> a -> m a |
|
201 |
checkRS RSNormal val = return val |
|
202 |
checkRS RSUnknown _ = fail "Unknown field" |
|
203 |
checkRS RSNoData _ = fail "No data for a field" |
|
204 |
checkRS RSUnavailable _ = fail "Ganeti reports unavailable data" |
|
205 |
checkRS RSOffline _ = fail "Ganeti reports resource as offline" |
|
206 |
|
|
207 | 187 |
-- | The end-of-message separator. |
208 | 188 |
eOM :: Word8 |
209 | 189 |
eOM = 3 |
b/htools/Ganeti/Qlang.hs | ||
---|---|---|
35 | 35 |
, FieldDefinition(..) |
36 | 36 |
, ResultEntry(..) |
37 | 37 |
, ItemType(..) |
38 |
, checkRS |
|
38 | 39 |
) where |
39 | 40 |
|
40 | 41 |
import Control.Applicative |
... | ... | |
58 | 59 |
]) |
59 | 60 |
$(makeJSONInstance ''ResultStatus) |
60 | 61 |
|
62 |
-- | Check that ResultStatus is success or fail with descriptive |
|
63 |
-- message. |
|
64 |
checkRS :: (Monad m) => ResultStatus -> a -> m a |
|
65 |
checkRS RSNormal val = return val |
|
66 |
checkRS RSUnknown _ = fail "Unknown field" |
|
67 |
checkRS RSNoData _ = fail "No data for a field" |
|
68 |
checkRS RSUnavail _ = fail "Ganeti reports unavailable data" |
|
69 |
checkRS RSOffline _ = fail "Ganeti reports resource as offline" |
|
70 |
|
|
61 | 71 |
-- | Type of a query field. |
62 | 72 |
$(declareSADT "FieldType" |
63 | 73 |
[ ("QFTUnknown", 'C.qftUnknown ) |
Also available in: Unified diff