blob: 05275c4b7bd789bf9c7a0774cf1001ad1bc3ae5e [file] [log] [blame]
{-| Implementation of the Ganeti confd server functionality.
-}
{-
Copyright (C) 2011, 2012, 2013 Google Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
-}
module Ganeti.Confd.Server
( main
, checkMain
, prepMain
) where
import Control.Applicative((<$>))
import Control.Concurrent
import Control.Monad (forever, liftM)
import Data.IORef
import Data.List
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import qualified Network.Socket as S
import System.Exit
import System.IO
import qualified Text.JSON as J
import Ganeti.BasicTypes
import Ganeti.Errors
import Ganeti.Daemon
import Ganeti.JSON
import Ganeti.Objects
import Ganeti.Confd.Types
import Ganeti.Confd.Utils
import Ganeti.Config
import Ganeti.ConfigReader
import Ganeti.Hash
import Ganeti.Logging
import qualified Ganeti.Constants as C
import qualified Ganeti.Query.Cluster as QCluster
import Ganeti.Utils
-- * Types and constants definitions
-- | What we store as configuration.
type CRef = IORef (Result (ConfigData, LinkIpMap))
-- | A small type alias for readability.
type StatusAnswer = (ConfdReplyStatus, J.JSValue)
-- | Unknown entry standard response.
queryUnknownEntry :: StatusAnswer
queryUnknownEntry = (ReplyStatusError, J.showJSON ConfdErrorUnknownEntry)
{- not used yet
-- | Internal error standard response.
queryInternalError :: StatusAnswer
queryInternalError = (ReplyStatusError, J.showJSON ConfdErrorInternal)
-}
-- | Argument error standard response.
queryArgumentError :: StatusAnswer
queryArgumentError = (ReplyStatusError, J.showJSON ConfdErrorArgument)
-- | Converter from specific error to a string format.
gntErrorToResult :: ErrorResult a -> Result a
gntErrorToResult (Bad err) = Bad (show err)
gntErrorToResult (Ok x) = Ok x
-- * Confd base functionality
-- | Computes the node role.
nodeRole :: ConfigData -> String -> Result ConfdNodeRole
nodeRole cfg name = do
cmaster <- errToResult $ QCluster.clusterMasterNodeName cfg
mnode <- errToResult $ getNode cfg name
let role = case mnode of
node | cmaster == name -> NodeRoleMaster
| nodeDrained node -> NodeRoleDrained
| nodeOffline node -> NodeRoleOffline
| nodeMasterCandidate node -> NodeRoleCandidate
_ -> NodeRoleRegular
return role
-- | Does an instance ip -> instance -> primary node -> primary ip
-- transformation.
getNodePipByInstanceIp :: ConfigData
-> LinkIpMap
-> String
-> String
-> StatusAnswer
getNodePipByInstanceIp cfg linkipmap link instip =
case M.lookup instip (M.findWithDefault M.empty link linkipmap) of
Nothing -> queryUnknownEntry
Just instname ->
case getInstPrimaryNode cfg instname of
Bad _ -> queryUnknownEntry -- either instance or node not found
Ok node -> (ReplyStatusOk, J.showJSON (nodePrimaryIp node))
-- | Returns a node name for a given UUID
uuidToNodeName :: ConfigData -> String -> Result String
uuidToNodeName cfg uuid = gntErrorToResult $ nodeName <$> getNode cfg uuid
-- | Encodes a list of minors into a JSON representation, converting UUIDs to
-- names in the process
encodeMinors :: ConfigData -> (String, Int, String, String, String, String)
-> Result J.JSValue
encodeMinors cfg (node_uuid, a, b, c, d, peer_uuid) = do
node_name <- uuidToNodeName cfg node_uuid
peer_name <- uuidToNodeName cfg peer_uuid
return . J.JSArray $ [J.showJSON node_name, J.showJSON a, J.showJSON b,
J.showJSON c, J.showJSON d, J.showJSON peer_name]
-- | Builds the response to a given query.
buildResponse :: (ConfigData, LinkIpMap) -> ConfdRequest -> Result StatusAnswer
buildResponse (cfg, _) (ConfdRequest { confdRqType = ReqPing }) =
return (ReplyStatusOk, J.showJSON (configVersion cfg))
buildResponse cdata req@(ConfdRequest { confdRqType = ReqClusterMaster }) =
case confdRqQuery req of
EmptyQuery -> liftM ((,) ReplyStatusOk . J.showJSON) master_name
PlainQuery _ -> return queryArgumentError
DictQuery reqq -> do
mnode <- gntErrorToResult $ getNode cfg master_uuid
mname <- master_name
let fvals = map (\field -> case field of
ReqFieldName -> mname
ReqFieldIp -> clusterMasterIp cluster
ReqFieldMNodePip -> nodePrimaryIp mnode
) (confdReqQFields reqq)
return (ReplyStatusOk, J.showJSON fvals)
where master_uuid = clusterMasterNode cluster
master_name = errToResult $ QCluster.clusterMasterNodeName cfg
cluster = configCluster cfg
cfg = fst cdata
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeRoleByName }) = do
node_name <- case confdRqQuery req of
PlainQuery str -> return str
_ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
role <- nodeRole (fst cdata) node_name
return (ReplyStatusOk, J.showJSON role)
buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipList }) =
-- note: we use foldlWithKey because that's present accross more
-- versions of the library
return (ReplyStatusOk, J.showJSON $
M.foldlWithKey (\accu _ n -> nodePrimaryIp n:accu) []
(fromContainer . configNodes . fst $ cdata))
buildResponse cdata (ConfdRequest { confdRqType = ReqMcPipList }) =
-- note: we use foldlWithKey because that's present accross more
-- versions of the library
return (ReplyStatusOk, J.showJSON $
M.foldlWithKey (\accu _ n -> if nodeMasterCandidate n
then nodePrimaryIp n:accu
else accu) []
(fromContainer . configNodes . fst $ cdata))
buildResponse (cfg, linkipmap)
req@(ConfdRequest { confdRqType = ReqInstIpsList }) = do
link <- case confdRqQuery req of
PlainQuery str -> return str
EmptyQuery -> return (getDefaultNicLink cfg)
_ -> fail "Invalid query type"
return (ReplyStatusOk, J.showJSON $ getInstancesIpByLink linkipmap link)
buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipByInstPip
, confdRqQuery = DictQuery query}) =
let (cfg, linkipmap) = cdata
link = fromMaybe (getDefaultNicLink cfg) (confdReqQLink query)
in case confdReqQIp query of
Just ip -> return $ getNodePipByInstanceIp cfg linkipmap link ip
Nothing -> return (ReplyStatusOk,
J.showJSON $
map (getNodePipByInstanceIp cfg linkipmap link)
(confdReqQIpList query))
buildResponse _ (ConfdRequest { confdRqType = ReqNodePipByInstPip }) =
return queryArgumentError
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeDrbd }) = do
let cfg = fst cdata
node_name <- case confdRqQuery req of
PlainQuery str -> return str
_ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
node <- gntErrorToResult $ getNode cfg node_name
let minors = concatMap (getInstMinorsForNode (nodeUuid node)) .
M.elems . fromContainer . configInstances $ cfg
encoded <- mapM (encodeMinors cfg) minors
return (ReplyStatusOk, J.showJSON encoded)
-- | Return the list of instances for a node (as ([primary], [secondary])) given
-- the node name.
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeInstances }) = do
let cfg = fst cdata
node_name <- case confdRqQuery req of
PlainQuery str -> return str
_ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
node <-
case getNode cfg node_name of
Ok n -> return n
Bad e -> fail $ "Node not found in the configuration: " ++ show e
let node_uuid = nodeUuid node
instances = getNodeInstances cfg node_uuid
return (ReplyStatusOk, J.showJSON instances)
-- | Creates a ConfdReply from a given answer.
serializeResponse :: Result StatusAnswer -> ConfdReply
serializeResponse r =
let (status, result) = case r of
Bad err -> (ReplyStatusError, J.showJSON err)
Ok (code, val) -> (code, val)
in ConfdReply { confdReplyProtocol = 1
, confdReplyStatus = status
, confdReplyAnswer = result
, confdReplySerial = 0 }
-- ** Client input/output handlers
-- | Main loop for a given client.
responder :: CRef -> S.Socket -> HashKey -> String -> S.SockAddr -> IO ()
responder cfgref socket hmac msg peer = do
ctime <- getCurrentTime
case parseRequest hmac msg ctime of
Ok (origmsg, rq) -> do
logDebug $ "Processing request: " ++ rStripSpace origmsg
mcfg <- readIORef cfgref
let response = respondInner mcfg hmac rq
_ <- S.sendTo socket response peer
logDebug $ "Response sent: " ++ response
return ()
Bad err -> logInfo $ "Failed to parse incoming message: " ++ err
return ()
-- | Inner helper function for a given client. This generates the
-- final encoded message (as a string), ready to be sent out to the
-- client.
respondInner :: Result (ConfigData, LinkIpMap) -> HashKey
-> ConfdRequest -> String
respondInner cfg hmac rq =
let rsalt = confdRqRsalt rq
innermsg = serializeResponse (cfg >>= flip buildResponse rq)
innerserialised = J.encodeStrict innermsg
outermsg = signMessage hmac rsalt innerserialised
outerserialised = confdMagicFourcc ++ J.encodeStrict outermsg
in outerserialised
-- | Main listener loop.
listener :: S.Socket -> HashKey
-> (S.Socket -> HashKey -> String -> S.SockAddr -> IO ())
-> IO ()
listener s hmac resp = do
(msg, _, peer) <- S.recvFrom s 4096
if confdMagicFourcc `isPrefixOf` msg
then forkIO (resp s hmac (drop 4 msg) peer) >> return ()
else logDebug "Invalid magic code!" >> return ()
return ()
-- | Type alias for prepMain results
type PrepResult = (S.Socket, IORef (Result (ConfigData, LinkIpMap)))
-- | Check function for confd.
checkMain :: CheckFn (S.Family, S.SockAddr)
checkMain opts = do
parseresult <- parseAddress opts C.defaultConfdPort
case parseresult of
Bad msg -> do
hPutStrLn stderr $ "parsing bind address: " ++ msg
return . Left $ ExitFailure 1
Ok v -> return $ Right v
-- | Prepare function for confd.
prepMain :: PrepFn (S.Family, S.SockAddr) PrepResult
prepMain _ (af_family, bindaddr) = do
s <- S.socket af_family S.Datagram S.defaultProtocol
S.bindSocket s bindaddr
cref <- newIORef (Bad "Configuration not yet loaded")
return (s, cref)
-- | Main function.
main :: MainFn (S.Family, S.SockAddr) PrepResult
main _ _ (s, cref) = do
let cfg_transform :: Result ConfigData -> Result (ConfigData, LinkIpMap)
cfg_transform = liftM (\cfg -> (cfg, buildLinkIpInstnameMap cfg))
initConfigReader cfg_transform cref
hmac <- getClusterHmac
-- enter the responder loop
forever $ listener s hmac (responder cref)