module Ganeti.Monitoring.Server
( main
, checkMain
, prepMain
, DataCollector(..)
) where
import Control.Applicative
import Control.DeepSeq (force)
import Control.Exception.Base (evaluate)
import Control.Monad
import Control.Monad.IO.Class
import Data.ByteString.Char8 (pack, unpack)
import Data.Maybe (fromMaybe)
import Data.List (find)
import Data.Monoid (mempty)
import qualified Data.Map as Map
import qualified Data.PSQueue as Queue
import Network.BSD (getServicePortNumber)
import Snap.Core
import Snap.Http.Server
import qualified Text.JSON as J
import Control.Concurrent
import qualified Ganeti.BasicTypes as BT
import Ganeti.Confd.Client
import Ganeti.Confd.Types
import qualified Ganeti.Confd.Types as CT
import Ganeti.Daemon
import qualified Ganeti.DataCollectors as DC
import Ganeti.DataCollectors.Types
import qualified Ganeti.JSON as GJ
import Ganeti.Objects (DataCollectorConfig(..))
import qualified Ganeti.Constants as C
import qualified Ganeti.ConstantUtils as CU
import Ganeti.Runtime
import Ganeti.Utils (getCurrentTimeUSec, withDefaultOnIOError)
type ConfigAccess = String -> DataCollectorConfig
type CheckResult = ()
type PrepResult = Config Snap ()
latestAPIVersion :: Int
latestAPIVersion = C.mondLatestApiVersion
defaultHttpConf :: FilePath -> FilePath -> Config Snap ()
defaultHttpConf accessLog errorLog =
setAccessLog (ConfigFileLog accessLog) .
setCompression False .
setErrorLog (ConfigFileLog errorLog) $
setVerbose False
emptyConfig
checkMain :: CheckFn CheckResult
checkMain _ = return $ Right ()
prepMain :: PrepFn CheckResult PrepResult
prepMain opts _ = do
accessLog <- daemonsExtraLogFile GanetiMond AccessLog
errorLog <- daemonsExtraLogFile GanetiMond ErrorLog
defaultPort <- withDefaultOnIOError C.defaultMondPort
. liftM fromIntegral
$ getServicePortNumber C.mond
return .
setPort
(maybe defaultPort fromIntegral (optPort opts)) .
maybe id (setBind . pack) (optBindAddress opts)
$ defaultHttpConf accessLog errorLog
versionQ :: Snap ()
versionQ = writeBS . pack $ J.encode [latestAPIVersion]
version1Api :: MVar CollectorMap -> MVar ConfigAccess -> Snap ()
version1Api mvar mvarConfig =
let returnNull = writeBS . pack $ J.encode J.JSNull :: Snap ()
in ifTop returnNull <|>
route
[ ("list", listHandler mvarConfig)
, ("report", reportHandler mvar mvarConfig)
]
collectorConfigs :: ConfdClient -> IO ConfigAccess
collectorConfigs confdClient = do
response <- query confdClient CT.ReqDataCollectors CT.EmptyQuery
return $ lookupConfig response
where
lookupConfig :: Maybe ConfdReply -> String -> DataCollectorConfig
lookupConfig response name = fromMaybe (mempty :: DataCollectorConfig) $ do
confdReply <- response
let answer = CT.confdReplyAnswer confdReply
case J.readJSON answer :: J.Result (GJ.Container DataCollectorConfig) of
J.Error _ -> Nothing
J.Ok container -> GJ.lookupContainer Nothing name container
activeCollectors :: MVar ConfigAccess -> IO [DataCollector]
activeCollectors mvarConfig = do
configs <- readMVar mvarConfig
return $ filter (dataCollectorActive . configs . dName) DC.collectors
dcListItem :: DataCollector -> J.JSValue
dcListItem dc =
J.JSArray
[ J.showJSON $ dName dc
, maybe defaultCategory J.showJSON $ dCategory dc
, J.showJSON $ dKind dc
]
where
defaultCategory = J.showJSON C.mondDefaultCategory
listHandler :: MVar ConfigAccess -> Snap ()
listHandler mvarConfig = dir "collectors" $ do
collectors' <- liftIO $ activeCollectors mvarConfig
writeBS . pack . J.encode $ map dcListItem collectors'
reportHandler :: MVar CollectorMap -> MVar ConfigAccess -> Snap ()
reportHandler mvar mvarConfig =
route
[ ("all", allReports mvar mvarConfig)
, (":category/:collector", oneReport mvar mvarConfig)
] <|>
errorReport
allReports :: MVar CollectorMap -> MVar ConfigAccess -> Snap ()
allReports mvar mvarConfig = do
collectors' <- liftIO $ activeCollectors mvarConfig
reports <- mapM (liftIO . getReport mvar) collectors'
writeBS . pack . J.encode $ reports
getReport :: MVar CollectorMap -> DataCollector -> IO DCReport
getReport mvar collector =
case dReport collector of
StatelessR r -> r
StatefulR r -> do
colData <- getColData (dName collector) mvar
r colData
getColData :: String -> MVar CollectorMap -> IO (Maybe CollectorData)
getColData name mvar = do
m <- readMVar mvar
return $ Map.lookup name m
catFromName :: String -> BT.Result (Maybe DCCategory)
catFromName "instance" = BT.Ok $ Just DCInstance
catFromName "storage" = BT.Ok $ Just DCStorage
catFromName "daemon" = BT.Ok $ Just DCDaemon
catFromName "hypervisor" = BT.Ok $ Just DCHypervisor
catFromName "default" = BT.Ok Nothing
catFromName _ = BT.Bad "No such category"
errorReport :: Snap ()
errorReport = do
modifyResponse $ setResponseStatus 404 "Not found"
writeBS "Unable to produce a report for the requested resource"
error404 :: Snap ()
error404 = do
modifyResponse $ setResponseStatus 404 "Not found"
writeBS "Resource not found"
oneReport :: MVar CollectorMap -> MVar ConfigAccess -> Snap ()
oneReport mvar mvarConfig = do
collectors' <- liftIO $ activeCollectors mvarConfig
categoryName <- maybe mzero unpack <$> getParam "category"
collectorName <- maybe mzero unpack <$> getParam "collector"
category <-
case catFromName categoryName of
BT.Ok cat -> return cat
BT.Bad msg -> fail msg
collector <-
case
find (\col -> collectorName == dName col) $
filter (\c -> category == dCategory c) collectors' of
Just col -> return col
Nothing -> fail "Unable to find the requested collector"
dcr <- liftIO $ getReport mvar collector
writeBS . pack . J.encode $ dcr
monitoringApi :: MVar CollectorMap -> MVar ConfigAccess -> Snap ()
monitoringApi mvar mvarConfig =
ifTop versionQ <|>
dir "1" (version1Api mvar mvarConfig) <|>
error404
collect :: CollectorMap -> DataCollector -> IO CollectorMap
collect m collector =
case dUpdate collector of
Nothing -> return m
Just update -> do
let name = dName collector
existing = Map.lookup name m
new_data <- update existing
_ <- evaluate $ force new_data
return $ Map.insert name new_data m
collection :: CollectorMap -> MVar ConfigAccess -> IO CollectorMap
collection m mvarConfig = do
collectors <- activeCollectors mvarConfig
foldM collect m collectors
seconds :: Int -> Integer
seconds = (* 1000000) . fromIntegral
collectord :: MVar CollectorMap -> MVar ConfigAccess -> IO ()
collectord mvar mvarConfig = do
let queue = Queue.fromAscList . map (Queue.:-> 0)
$ CU.toList C.dataCollectorNames
foldM_ update queue [0::Integer ..]
where
resetTimer configs = Queue.adjustWithKey ((+) . dataCollectorInterval
. configs)
resetAll configs = foldr (resetTimer configs)
keyInList = flip . const . flip elem
update q _ = do
t <- getCurrentTimeUSec
configs <- readMVar mvarConfig
m <- takeMVar mvar
let dueNames = map Queue.key $ Queue.atMost t q
dueEntries = Map.filterWithKey (keyInList dueNames) m
m' <- collection dueEntries mvarConfig
let m'' = m' `Map.union` m
putMVar mvar m''
let q' = resetAll configs q dueNames
maxSleep = seconds C.mondTimeInterval
nextWakeup = fromMaybe maxSleep . liftM Queue.prio $ Queue.findMin q'
delay = min maxSleep nextWakeup
threadDelay $ fromInteger delay
return q'
main :: MainFn CheckResult PrepResult
main _ _ httpConf = do
mvarCollectorMap <- newMVar Map.empty
mvarConfig <- newEmptyMVar
confdClient <- getConfdClient Nothing Nothing
void . forkIO . forever $ do
configs <- collectorConfigs confdClient
putMVar mvarConfig configs
threadDelay . fromInteger $ seconds C.mondConfigTimeInterval
takeMVar mvarConfig
void . forkIO $ collectord mvarCollectorMap mvarConfig
httpServe httpConf . method GET $ monitoringApi mvarCollectorMap mvarConfig