and add it to the data collector list.
Signed-off-by: BSRK Aditya <[email protected]>
---
src/Ganeti/DataCollectors.hs | 5 ++
src/Ganeti/DataCollectors/Diagnose.hs | 147 +++++++++++++++++++++++++++++++++
src/Ganeti/DataCollectors/Types.hs | 2 +-
3 files changed, 153 insertions(+), 1 deletion(-)
create mode 100644 src/Ganeti/DataCollectors/Diagnose.hs
diff --git a/src/Ganeti/DataCollectors.hs b/src/Ganeti/DataCollectors.hs
index bca6848..9c2cc3b 100644
--- a/src/Ganeti/DataCollectors.hs
+++ b/src/Ganeti/DataCollectors.hs
@@ -38,6 +38,7 @@ import Data.Map (findWithDefault)
import Data.Monoid (mempty)
import qualified Ganeti.DataCollectors.CPUload as CPUload
+import qualified Ganeti.DataCollectors.Diagnose as Diagnose
import qualified Ganeti.DataCollectors.Diskstats as Diskstats
import qualified Ganeti.DataCollectors.Drbd as Drbd
import qualified Ganeti.DataCollectors.InstStatus as InstStatus
@@ -57,6 +58,7 @@ collectors =
, drdbCollector
, instStatusCollector
, lvCollector
+ , diagnoseCollector
]
where
f .&&. g = \x y -> f x y && g x y
@@ -82,6 +84,9 @@ collectors =
lvCollector =
DataCollector Lv.dcName Lv.dcCategory Lv.dcKind
(StatelessR Lv.dcReport) Nothing activeConfig updateInterval
+ diagnoseCollector =
+ DataCollector Diagnose.dcName Diagnose.dcCategory Diagnose.dcKind
+ (StatelessR Diagnose.dcReport) Nothing activeConfig updateInterval
cpuLoadCollector =
DataCollector CPUload.dcName CPUload.dcCategory CPUload.dcKind
(StatefulR CPUload.dcReport) (Just CPUload.dcUpdate) activeConfig
diff --git a/src/Ganeti/DataCollectors/Diagnose.hs
b/src/Ganeti/DataCollectors/Diagnose.hs
new file mode 100644
index 0000000..43c6c30
--- /dev/null
+++ b/src/Ganeti/DataCollectors/Diagnose.hs
@@ -0,0 +1,147 @@
+{-| Self-diagnose data collector
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Ganeti.DataCollectors.Diagnose
+ ( dcName
+ , dcCategory
+ , dcKind
+ , dcReport
+ ) where
+
+import Control.Monad.Trans.Class (lift)
+import System.Directory (doesFileExist)
+import System.FilePath.Posix (isValid, takeFileName, (</>))
+import System.Posix.Files (getFileStatus, fileOwner, fileGroup,
+ fileMode, ownerModes, groupReadMode,
+ groupExecuteMode, otherReadMode,
+ otherExecuteMode, intersectFileModes,
+ unionFileModes, ownerExecuteMode,
+ isRegularFile)
+import System.Process (readProcess)
+import Text.JSON (JSValue(..), toJSObject, toJSString, decode, Result(..))
+
+import Ganeti.BasicTypes (runResultT, ResultT(..), genericResult)
+import Ganeti.Config (loadConfig)
+import Ganeti.Constants (dataCollectorDiagnose, dataCollectorDiagnoseDirectory)
+import Ganeti.DataCollectors.Types (DCCategory(..), DCKind(..), DCVersion(..),
+ DCReport(..), buildReport)
+import Ganeti.Objects (configCluster, clusterDiagnoseDataCollectorFilename)
+import Ganeti.Path (clusterConfFile)
+
+-- | The name of this data collector.
+dcName :: String
+dcName = dataCollectorDiagnose
+
+-- | The category of this data collector.
+dcCategory :: Maybe DCCategory
+dcCategory = Just DCNode
+
+-- | The kind of this data collector.
+dcKind :: DCKind
+dcKind = DCKStatus
+
+-- | The version of this data collector.
+dcVersion :: DCVersion
+dcVersion = DCVerBuiltin
+
+-- | The version number for the data format of this data collector.
+dcFormatVersion :: Int
+dcFormatVersion = 1
+
+okWithDetails :: String -> JSValue
+okWithDetails details = JSObject $ toJSObject
+ [ ("status", JSString $ toJSString "Ok")
+ , ("details", JSString $ toJSString details)
+ ]
+
+
+fnToVal :: String -> IO JSValue
+fnToVal fn
+ | null fn = return $ okWithDetails
+ "No file specified for diagnose data collector"
+ | not $ isValid fn = return $ okWithDetails
+ "Invalid filename specified for diagnose data collector"
+ | takeFileName fn /= fn = return $ okWithDetails
+ "Filepaths cannot be specified for diagnose data collector"
+ | otherwise = do
+ let fp = dataCollectorDiagnoseDirectory </> fn
+ exists <- doesFileExist fp
+ if exists
+ then do
+ fs <- getFileStatus fp
+ let maxFileMode = ownerModes `unionFileModes` groupReadMode
+ `unionFileModes` groupExecuteMode
+ `unionFileModes` otherReadMode
+ `unionFileModes` otherExecuteMode
+ isSubSetOf m1 m2 = m1 `intersectFileModes` m2 == m1
+ case () of _
+ | fileOwner fs /= 0 -> return $ okWithDetails
+ "File for diagnose data collector must be owned by root"
+ | fileGroup fs /= 0 -> return $ okWithDetails
+ "File for diagnose data collector must have group root"
+ | isSubSetOf (fileMode fs) maxFileMode ->
+ return . okWithDetails $
+ "File for diagnose data collector " ++
+ "must have permissions 755 or stricter"
+ | isSubSetOf ownerExecuteMode (fileMode fs) ->
+ return . okWithDetails $
+ "File for diagnose data collector " ++
+ "must be executable by owner"
+ | isSubSetOf groupExecuteMode (fileMode fs) ->
+ return . okWithDetails $
+ "File for diagnose data collector " ++
+ "must be executable by group"
+ | not $ isRegularFile fs ->
+ return . okWithDetails $
+ "File for diagnose data collector " ++
+ "must be a regular file"
+ | otherwise -> do
+ r <- fmap decode (readProcess fp [] "")
+ case r of
+ Ok val -> return val
+ Error str -> return . okWithDetails $
+ "Could not parse result: " ++ str
+ else return $ okWithDetails
+ "File specified for diagnose data collector does not exist"
+
+buildJsonReport :: IO JSValue
+buildJsonReport = fmap (genericResult okWithDetails id) . runResultT $ do
+ configData <- ResultT (clusterConfFile >>= loadConfig)
+ lift . fnToVal . clusterDiagnoseDataCollectorFilename $
+ configCluster configData
+
+-- | The data exported by the data collector, taken from the default location.
+dcReport :: IO DCReport
+dcReport = buildJsonReport >>=
+ buildReport dcName dcVersion dcFormatVersion dcCategory dcKind
diff --git a/src/Ganeti/DataCollectors/Types.hs
b/src/Ganeti/DataCollectors/Types.hs
index 8b60be1..3bd31b3 100644
--- a/src/Ganeti/DataCollectors/Types.hs
+++ b/src/Ganeti/DataCollectors/Types.hs
@@ -68,7 +68,7 @@ import Ganeti.THH
import Ganeti.Utils (getCurrentTimeUSec)
-- | The possible classes a data collector can belong to.
-data DCCategory = DCInstance | DCStorage | DCDaemon | DCHypervisor
+data DCCategory = DCInstance | DCStorage | DCDaemon | DCHypervisor | DCNode
deriving (Show, Eq, Read, Enum, Bounded)
-- | Get the category name and return it as a string.
--
1.7.10.4