1 {-| Implementation of the Ganeti confd server functionality.
7 Copyright (C) 2011, 2012, 2013 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.Confd.Server
32 import Control.Concurrent
33 import Control.Monad (forever, liftM)
36 import qualified Data.Map as M
37 import Data.Maybe (fromMaybe)
38 import qualified Network.Socket as S
41 import qualified Text.JSON as J
43 import Ganeti.BasicTypes
48 import Ganeti.Confd.Types
49 import Ganeti.Confd.Utils
51 import Ganeti.ConfigReader
54 import qualified Ganeti.Constants as C
55 import qualified Ganeti.Query.Cluster as QCluster
58 -- * Types and constants definitions
60 -- | What we store as configuration.
61 type CRef = IORef (Result (ConfigData, LinkIpMap))
63 -- | A small type alias for readability.
64 type StatusAnswer = (ConfdReplyStatus, J.JSValue)
66 -- | Unknown entry standard response.
67 queryUnknownEntry :: StatusAnswer
68 queryUnknownEntry = (ReplyStatusError, J.showJSON ConfdErrorUnknownEntry)
71 -- | Internal error standard response.
72 queryInternalError :: StatusAnswer
73 queryInternalError = (ReplyStatusError, J.showJSON ConfdErrorInternal)
76 -- | Argument error standard response.
77 queryArgumentError :: StatusAnswer
78 queryArgumentError = (ReplyStatusError, J.showJSON ConfdErrorArgument)
80 -- | Converter from specific error to a string format.
81 gntErrorToResult :: ErrorResult a -> Result a
82 gntErrorToResult (Bad err) = Bad (show err)
83 gntErrorToResult (Ok x) = Ok x
85 -- * Confd base functionality
87 -- | Computes the node role.
88 nodeRole :: ConfigData -> String -> Result ConfdNodeRole
89 nodeRole cfg name = do
90 cmaster <- errToResult $ QCluster.clusterMasterNodeName cfg
91 mnode <- errToResult $ getNode cfg name
92 let role = case mnode of
93 node | cmaster == name -> NodeRoleMaster
94 | nodeDrained node -> NodeRoleDrained
95 | nodeOffline node -> NodeRoleOffline
96 | nodeMasterCandidate node -> NodeRoleCandidate
100 -- | Does an instance ip -> instance -> primary node -> primary ip
102 getNodePipByInstanceIp :: ConfigData
107 getNodePipByInstanceIp cfg linkipmap link instip =
108 case M.lookup instip (M.findWithDefault M.empty link linkipmap) of
109 Nothing -> queryUnknownEntry
111 case getInstPrimaryNode cfg instname of
112 Bad _ -> queryUnknownEntry -- either instance or node not found
113 Ok node -> (ReplyStatusOk, J.showJSON (nodePrimaryIp node))
115 -- | Builds the response to a given query.
116 buildResponse :: (ConfigData, LinkIpMap) -> ConfdRequest -> Result StatusAnswer
117 buildResponse (cfg, _) (ConfdRequest { confdRqType = ReqPing }) =
118 return (ReplyStatusOk, J.showJSON (configVersion cfg))
120 buildResponse cdata req@(ConfdRequest { confdRqType = ReqClusterMaster }) =
121 case confdRqQuery req of
122 EmptyQuery -> liftM ((,) ReplyStatusOk . J.showJSON) master_name
123 PlainQuery _ -> return queryArgumentError
125 mnode <- gntErrorToResult $ getNode cfg master_uuid
127 let fvals = map (\field -> case field of
128 ReqFieldName -> mname
129 ReqFieldIp -> clusterMasterIp cluster
130 ReqFieldMNodePip -> nodePrimaryIp mnode
131 ) (confdReqQFields reqq)
132 return (ReplyStatusOk, J.showJSON fvals)
133 where master_uuid = clusterMasterNode cluster
134 master_name = errToResult $ QCluster.clusterMasterNodeName cfg
135 cluster = configCluster cfg
138 buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeRoleByName }) = do
139 node_name <- case confdRqQuery req of
140 PlainQuery str -> return str
141 _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
142 role <- nodeRole (fst cdata) node_name
143 return (ReplyStatusOk, J.showJSON role)
145 buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipList }) =
146 -- note: we use foldlWithKey because that's present accross more
147 -- versions of the library
148 return (ReplyStatusOk, J.showJSON $
149 M.foldlWithKey (\accu _ n -> nodePrimaryIp n:accu) []
150 (fromContainer . configNodes . fst $ cdata))
152 buildResponse cdata (ConfdRequest { confdRqType = ReqMcPipList }) =
153 -- note: we use foldlWithKey because that's present accross more
154 -- versions of the library
155 return (ReplyStatusOk, J.showJSON $
156 M.foldlWithKey (\accu _ n -> if nodeMasterCandidate n
157 then nodePrimaryIp n:accu
159 (fromContainer . configNodes . fst $ cdata))
161 buildResponse (cfg, linkipmap)
162 req@(ConfdRequest { confdRqType = ReqInstIpsList }) = do
163 link <- case confdRqQuery req of
164 PlainQuery str -> return str
165 EmptyQuery -> return (getDefaultNicLink cfg)
166 _ -> fail "Invalid query type"
167 return (ReplyStatusOk, J.showJSON $ getInstancesIpByLink linkipmap link)
169 buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipByInstPip
170 , confdRqQuery = DictQuery query}) =
171 let (cfg, linkipmap) = cdata
172 link = fromMaybe (getDefaultNicLink cfg) (confdReqQLink query)
173 in case confdReqQIp query of
174 Just ip -> return $ getNodePipByInstanceIp cfg linkipmap link ip
175 Nothing -> return (ReplyStatusOk,
177 map (getNodePipByInstanceIp cfg linkipmap link)
178 (confdReqQIpList query))
180 buildResponse _ (ConfdRequest { confdRqType = ReqNodePipByInstPip }) =
181 return queryArgumentError
183 buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeDrbd }) = do
185 node_name <- case confdRqQuery req of
186 PlainQuery str -> return str
187 _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
188 node <- gntErrorToResult $ getNode cfg node_name
189 let minors = concatMap (getInstMinorsForNode (nodeName node)) .
190 M.elems . fromContainer . configInstances $ cfg
191 encoded = [J.JSArray [J.showJSON a, J.showJSON b, J.showJSON c,
192 J.showJSON d, J.showJSON e, J.showJSON f] |
193 (a, b, c, d, e, f) <- minors]
194 return (ReplyStatusOk, J.showJSON encoded)
196 buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeInstances }) = do
198 node_name <- case confdRqQuery req of
199 PlainQuery str -> return str
200 _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
201 let instances = getNodeInstances cfg node_name
202 return (ReplyStatusOk, J.showJSON instances)
204 -- | Creates a ConfdReply from a given answer.
205 serializeResponse :: Result StatusAnswer -> ConfdReply
206 serializeResponse r =
207 let (status, result) = case r of
208 Bad err -> (ReplyStatusError, J.showJSON err)
209 Ok (code, val) -> (code, val)
210 in ConfdReply { confdReplyProtocol = 1
211 , confdReplyStatus = status
212 , confdReplyAnswer = result
213 , confdReplySerial = 0 }
215 -- ** Client input/output handlers
217 -- | Main loop for a given client.
218 responder :: CRef -> S.Socket -> HashKey -> String -> S.SockAddr -> IO ()
219 responder cfgref socket hmac msg peer = do
220 ctime <- getCurrentTime
221 case parseRequest hmac msg ctime of
222 Ok (origmsg, rq) -> do
223 logDebug $ "Processing request: " ++ rStripSpace origmsg
224 mcfg <- readIORef cfgref
225 let response = respondInner mcfg hmac rq
226 _ <- S.sendTo socket response peer
228 Bad err -> logInfo $ "Failed to parse incoming message: " ++ err
231 -- | Inner helper function for a given client. This generates the
232 -- final encoded message (as a string), ready to be sent out to the
234 respondInner :: Result (ConfigData, LinkIpMap) -> HashKey
235 -> ConfdRequest -> String
236 respondInner cfg hmac rq =
237 let rsalt = confdRqRsalt rq
238 innermsg = serializeResponse (cfg >>= flip buildResponse rq)
239 innerserialised = J.encodeStrict innermsg
240 outermsg = signMessage hmac rsalt innerserialised
241 outerserialised = confdMagicFourcc ++ J.encodeStrict outermsg
244 -- | Main listener loop.
245 listener :: S.Socket -> HashKey
246 -> (S.Socket -> HashKey -> String -> S.SockAddr -> IO ())
248 listener s hmac resp = do
249 (msg, _, peer) <- S.recvFrom s 4096
250 if confdMagicFourcc `isPrefixOf` msg
251 then forkIO (resp s hmac (drop 4 msg) peer) >> return ()
252 else logDebug "Invalid magic code!" >> return ()
255 -- | Type alias for prepMain results
256 type PrepResult = (S.Socket, IORef (Result (ConfigData, LinkIpMap)))
258 -- | Check function for confd.
259 checkMain :: CheckFn (S.Family, S.SockAddr)
261 parseresult <- parseAddress opts C.defaultConfdPort
264 hPutStrLn stderr $ "parsing bind address: " ++ msg
265 return . Left $ ExitFailure 1
266 Ok v -> return $ Right v
268 -- | Prepare function for confd.
269 prepMain :: PrepFn (S.Family, S.SockAddr) PrepResult
270 prepMain _ (af_family, bindaddr) = do
271 s <- S.socket af_family S.Datagram S.defaultProtocol
272 S.bindSocket s bindaddr
273 cref <- newIORef (Bad "Configuration not yet loaded")
277 main :: MainFn (S.Family, S.SockAddr) PrepResult
278 main _ _ (s, cref) = do
279 let cfg_transform :: Result ConfigData -> Result (ConfigData, LinkIpMap)
280 cfg_transform = liftM (\cfg -> (cfg, buildLinkIpInstnameMap cfg))
281 initConfigReader cfg_transform cref
283 hmac <- getClusterHmac
284 -- enter the responder loop
285 forever $ listener s hmac (responder cref)