and add it to the data collector list.
Add the new file to HS_LIB_SRCS in Makefile.am

Signed-off-by: BSRK Aditya <[email protected]>
---
 Makefile.am                           |    1 +
 src/Ganeti/DataCollectors.hs          |    5 ++
 src/Ganeti/DataCollectors/Diagnose.hs |  147 +++++++++++++++++++++++++++++++++
 src/Ganeti/DataCollectors/Types.hs    |    2 +-
 src/Ganeti/Monitoring/Server.hs       |    1 +
 5 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 src/Ganeti/DataCollectors/Diagnose.hs

diff --git a/Makefile.am b/Makefile.am
index a39e15c..920a4da 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -899,6 +899,7 @@ HS_LIB_SRCS = \
        src/Ganeti/DataCollectors.hs \
        src/Ganeti/DataCollectors/CLI.hs \
        src/Ganeti/DataCollectors/CPUload.hs \
+       src/Ganeti/DataCollectors/Diagnose.hs \
        src/Ganeti/DataCollectors/Diskstats.hs \
        src/Ganeti/DataCollectors/Drbd.hs \
        src/Ganeti/DataCollectors/InstStatus.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..7491978
--- /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, regularFileMode)
+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 = foldl1 unionFileModes [ ownerModes
+                                                  , groupReadMode
+                                                  , groupExecuteMode
+                                                  , otherReadMode
+                                                  , otherExecuteMode
+                                                  , regularFileMode
+                                                  ]
+              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"
+                       | not $ isRegularFile fs -> return . okWithDetails $
+                         "File for diagnose data collector " ++
+                         "must be a regular file"
+                       | not $ isSubSetOf (fileMode fs) maxFileMode ->
+                         return . okWithDetails $
+                           "File for diagnose data collector " ++
+                           "must have permissions 755 or stricter"
+                       | not $ isSubSetOf ownerExecuteMode (fileMode fs) ->
+                         return . okWithDetails $
+                           "File for diagnose data collector " ++
+                           "must be executable by owner"
+                       | 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.
diff --git a/src/Ganeti/Monitoring/Server.hs b/src/Ganeti/Monitoring/Server.hs
index 333d0d1..530fd5b 100644
--- a/src/Ganeti/Monitoring/Server.hs
+++ b/src/Ganeti/Monitoring/Server.hs
@@ -190,6 +190,7 @@ 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 "node"       = BT.Ok $ Just DCNode
 catFromName "default"    = BT.Ok Nothing
 catFromName _            = BT.Bad "No such category"
 
-- 
1.7.10.4

Reply via email to