module Ganeti.Monitoring.Server
( main
, checkMain
, prepMain
) 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 hiding (map, filter, find)
import Data.List
import qualified Data.Map as Map
import Snap.Core
import Snap.Http.Server
import qualified Text.JSON as J
import Control.Concurrent
import qualified Ganeti.BasicTypes as BT
import Ganeti.Daemon
import qualified Ganeti.DataCollectors.CPUload as CPUload
import qualified Ganeti.DataCollectors.Diskstats as Diskstats
import qualified Ganeti.DataCollectors.Drbd as Drbd
import qualified Ganeti.DataCollectors.InstStatus as InstStatus
import qualified Ganeti.DataCollectors.Lv as Lv
import Ganeti.DataCollectors.Types
import qualified Ganeti.Constants as C
import Ganeti.Runtime
type CheckResult = ()
type PrepResult = Config Snap ()
latestAPIVersion :: Int
latestAPIVersion = C.mondLatestApiVersion
data Report = StatelessR (IO DCReport)
| StatefulR (Maybe CollectorData -> IO DCReport)
data DataCollector = DataCollector
{ dName :: String
, dCategory :: Maybe DCCategory
, dKind :: DCKind
, dReport :: Report
, dUpdate :: Maybe (Maybe CollectorData -> IO CollectorData)
}
collectors :: [DataCollector]
collectors =
[ DataCollector Diskstats.dcName Diskstats.dcCategory Diskstats.dcKind
(StatelessR Diskstats.dcReport) Nothing
, DataCollector Drbd.dcName Drbd.dcCategory Drbd.dcKind
(StatelessR Drbd.dcReport) Nothing
, DataCollector InstStatus.dcName InstStatus.dcCategory InstStatus.dcKind
(StatelessR InstStatus.dcReport) Nothing
, DataCollector Lv.dcName Lv.dcCategory Lv.dcKind
(StatelessR Lv.dcReport) Nothing
, DataCollector CPUload.dcName CPUload.dcCategory CPUload.dcKind
(StatefulR CPUload.dcReport) (Just CPUload.dcUpdate)
]
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
return .
setPort
(maybe C.defaultMondPort fromIntegral (optPort opts)) .
maybe id (setBind . pack) (optBindAddress opts)
$ defaultHttpConf accessLog errorLog
versionQ :: Snap ()
versionQ = writeBS . pack $ J.encode [latestAPIVersion]
version1Api :: MVar CollectorMap -> Snap ()
version1Api mvar =
let returnNull = writeBS . pack $ J.encode J.JSNull :: Snap ()
in ifTop returnNull <|>
route
[ ("list", listHandler)
, ("report", reportHandler mvar)
]
dcListItem :: DataCollector -> J.JSValue
dcListItem dc =
J.JSArray
[ J.showJSON $ dName dc
, maybe J.JSNull J.showJSON $ dCategory dc
, J.showJSON $ dKind dc
]
listHandler :: Snap ()
listHandler =
dir "collectors" . writeBS . pack . J.encode $ map dcListItem collectors
reportHandler :: MVar CollectorMap -> Snap ()
reportHandler mvar =
route
[ ("all", allReports mvar)
, (":category/:collector", oneReport mvar)
] <|>
errorReport
allReports :: MVar CollectorMap -> Snap ()
allReports mvar = do
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 -> Snap ()
oneReport mvar = do
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 -> Snap ()
monitoringApi mvar =
ifTop versionQ <|>
dir "1" (version1Api mvar) <|>
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 -> IO CollectorMap
collection m = foldM collect m collectors
collectord :: MVar CollectorMap -> IO ()
collectord mvar = do
m <- takeMVar mvar
m' <- collection m
putMVar mvar m'
threadDelay $ 10^(6 :: Int) * C.mondTimeInterval
collectord mvar
main :: MainFn CheckResult PrepResult
main _ _ httpConf = do
mvar <- newMVar Map.empty
_ <- forkIO $ collectord mvar
httpServe httpConf . method GET $ monitoringApi mvar