A new directory for haskell modules about block devices has been created
The parser is divided in two modules:
* one exports the data types describing the DRBD status
* one exports the parser itself
Signed-off-by: Michele Tartara <
mtar...@google.com>
---
Makefile.am | 5 +
htools/Ganeti/Block/DRBDDataTypes.hs | 165 +++++++++++++++++
htools/Ganeti/Block/DRBDParser.hs | 331 ++++++++++++++++++++++++++++++++++
3 files changed, 501 insertions(+), 0 deletions(-)
create mode 100644 htools/Ganeti/Block/DRBDDataTypes.hs
create mode 100644 htools/Ganeti/Block/DRBDParser.hs
diff --git a/Makefile.am b/Makefile.am
index 2ca6ed7..13119f0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -57,6 +57,7 @@ myexeclibdir = $(pkglibdir)
HTOOLS_DIRS = \
htools \
htools/Ganeti \
+ htools/Ganeti/Block \
htools/Ganeti/Confd \
htools/Ganeti/HTools \
htools/Ganeti/HTools/Backend \
@@ -108,6 +109,7 @@ BUILDTIME_DIR_AUTOCREATE = \
$(APIDOC_DIR) \
$(APIDOC_HS_DIR) \
$(APIDOC_HS_DIR)/Ganeti \
+ $(APIDOC_HS_DIR)/Ganeti/Block \
$(APIDOC_HS_DIR)/Ganeti/Confd \
$(APIDOC_HS_DIR)/Ganeti/HTools \
$(APIDOC_HS_DIR)/Ganeti/HTools/Backend \
@@ -424,6 +426,8 @@ HPCEXCL = --exclude Main \
$(patsubst htools.%,--exclude Test.%,$(subst /,.,$(patsubst %.hs,%, $(HS_LIB_SRCS))))
HS_LIB_SRCS = \
+ htools/Ganeti/Block/DRBDDataTypes.hs \
+ htools/Ganeti/Block/DRBDParser.hs \
htools/Ganeti/BasicTypes.hs \
htools/Ganeti/Common.hs \
htools/Ganeti/Compat.hs \
@@ -1608,6 +1612,7 @@ hs-apidoc: $(HS_BUILT_SRCS)
rm -rf $(APIDOC_HS_DIR)/*
@mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/HTools/Backend
@mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/HTools/Program
+ @mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/Block
@mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/Confd
@mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/Query
$(HSCOLOUR) -print-css > $(APIDOC_HS_DIR)/Ganeti/hscolour.css
diff --git a/htools/Ganeti/Block/DRBDDataTypes.hs b/htools/Ganeti/Block/DRBDDataTypes.hs
new file mode 100644
index 0000000..19b7ecf
--- /dev/null
+++ b/htools/Ganeti/Block/DRBDDataTypes.hs
@@ -0,0 +1,165 @@
+{-| DRBD Data Types
+
+This module holds the definition of the data types describing the status of
+DRBD.
+
+-}
+{-
+
+Copyright (C) 2012 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.Block.DRBDDataTypes (DRBDStatus(..),
+ VersionInfo(..),
+ DeviceInfo(..),
+ ConnectionState(..),
+ LocalRemote(..),
+ Role(..),
+ DiskState(..),
+ PerformanceIndicators(..),
+ SyncStatus(..),
+ SizeUnit(..),
+ Time(..),
+ TimeUnit(..),
+ AdditionalInfo(..)) where
+
+-- | Data type contaning all the data about the status of DRBD
+data DRBDStatus = DRBDStatus { versionInfo::VersionInfo,
+ deviceInfo::[DeviceInfo] } deriving (Eq, Show)
+
+-- | Data type describing the DRBD version
+data VersionInfo = VersionInfo { version::Maybe String,
+ api::Maybe String,
+ proto::Maybe String,
+ srcversion::Maybe String,
+ gitHash::Maybe String,
+ buildBy::Maybe String } deriving (Eq, Show)
+
+-- | Data type describing a device
+data DeviceInfo = UnconfiguredDevice Int |
+ DeviceInfo { deviceNumber::Int,
+ connectionState::ConnectionState,
+ resourceRoles::LocalRemote Role,
+ diskStates::LocalRemote DiskState,
+ replicationProtocol::Char,
+ ioFlags::String,
+ performanceIndicators::PerformanceIndicators,
+ syncStatus::Maybe SyncStatus,
+ resync::Maybe AdditionalInfo,
+ actLog::Maybe AdditionalInfo
+ } deriving (Eq, Show)
+
+-- | Data type describing the state of the connection
+data ConnectionState = StandAlone
+ | Disconnecting
+ | Unconnected
+ | Timeout
+ | BrokenPipe
+ | NetworkFailure
+ | ProtocolError
+ | TearDown
+ | WFConnection
+ | WFReportParams
+ | Connected
+ | StartingSyncS
+ | StartingSyncT
+ | WFBitMapS
+ | WFBitMapT
+ | WFSyncUUID
+ | SyncSource
+ | SyncTarget
+ | PausedSyncS
+ | PausedSyncT
+ | VerifyS
+ | VerifyT
+ | Unconfigured deriving (Show, Eq)
+
+-- | Algebraic data type describing something that has a local and a remote
+-- value
+data LocalRemote a = LocalRemote { local::a,
+ remote::a
+ } deriving (Eq, Show)
+
+-- | Data type describing
+data Role = Primary
+ | Secondary
+ | Unknown deriving (Eq, Show)
+
+-- | Data type describing disk states
+data DiskState = Diskless
+ | Attaching
+ | Failed
+ | Negotiating
+ | Inconsistent
+ | Outdated
+ | DUnknown
+ | Consistent
+ | UpToDate deriving (Eq, Show)
+
+-- | Data type containing data about performance indicators
+data PerformanceIndicators = PerformanceIndicators {
+ networkSend::Int,
+ networkReceive::Int,
+ diskWrite::Int,
+ diskRead::Int,
+ activityLog::Int,
+ bitMap::Int,
+ localCount::Int,
+ pending::Int,
+ unacknowledged::Int,
+ applicationPending::Int,
+ epochs::Maybe Int,
+ writeOrder::Maybe Char,
+ outOfSync::Maybe Int } deriving (Eq, Show)
+
+-- | Data type containing data about the synchronization status of a device
+data SyncStatus = SyncStatus {
+ percentage::Double,
+ partialSyncSize::Int,
+ totalSyncSize::Int,
+ syncUnit::SizeUnit,
+ timeToFinish::Time,
+ speed::Double,
+ want::Maybe Double,
+ speedSizeUnit::SizeUnit,
+ speedTimeUnit::TimeUnit
+ } deriving (Eq, Show)
+
+-- | Data type describing a size unit for memory
+data SizeUnit = KiloByte | MegaByte deriving (Eq, Show)
+
+-- | Data type describing a time (hh:mm:ss)
+data Time = Time {
+ hour::Integer,
+ min::Integer,
+ sec::Integer
+ } deriving (Eq, Show)
+
+-- | Data type describing a time unit
+data TimeUnit = Second deriving (Eq, Show)
+
+-- | Additional device-specific information produced by drbd <= 8.0
+data AdditionalInfo = AdditionalInfo {
+ partialUsed::Int,
+ totalUsed::Int,
+ hits::Int,
+ misses::Int,
+ starving::Int,
+ dirty::Int,
+ changed::Int
+ } deriving (Eq, Show)
diff --git a/htools/Ganeti/Block/DRBDParser.hs b/htools/Ganeti/Block/DRBDParser.hs
new file mode 100644
index 0000000..9ed805d
--- /dev/null
+++ b/htools/Ganeti/Block/DRBDParser.hs
@@ -0,0 +1,331 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-| DRBD proc file parser
+
+This module holds the definition of the parser that extracts status information
+from the DRBD proc file
+
+-}
+{-
+
+Copyright (C) 2012 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.Block.DRBDParser (drbdStatusParser) where
+
+import Control.Applicative ((<*>), (*>), (<*), (<$>), (<|>), pure, some)
+import qualified Data.Attoparsec.Text as A
+import qualified Data.Attoparsec.Combinator as AC
+import Data.Attoparsec.Text (Parser)
+import Data.Text (Text, unpack)
+
+import Ganeti.Block.DRBDDataTypes
+
+-- | Our own space-skipping function, because A.skipSpace also skips
+-- newline characters. It skips ZERO or more spaces, so it does not fail if
+-- there are no spaces
+skipSpaces :: Parser ()
+skipSpaces = A.skipWhile A.isHorizontalSpace
+
+-- | Skips spaces and the given string, then executes a parser and
+-- returns its result
+skipSpacesAndString :: Text -> Parser a -> Parser a
+skipSpacesAndString s parser = skipSpaces
+ *> A.string s
+ *> parser
+
+-- | Takes a parser and returns it with the content wrapped in a Maybe
+-- object. The resulting parser never fails, but contains Nothing if it
+-- couldn't properly parse the string.
+optional :: Parser a -> Parser (Maybe a)
+optional parser = (Just <$> parser) <|> pure Nothing
+
+-- | The parser for a whole DRBD status file
+drbdStatusParser :: Parser DRBDStatus
+drbdStatusParser = DRBDStatus <$> versionInfoParser
+ <*> deviceParser `AC.manyTill` A.endOfInput
+
+-- | The parser for the version information lines
+versionInfoParser :: Parser VersionInfo
+versionInfoParser = VersionInfo <$> optional vers
+ <*> optional a
+ <*> optional p
+ <*> optional srcVersion
+ <*> (fmap unpack <$> optional gh)
+ <*> (fmap unpack <$> optional builder)
+ where vers = A.string "version:"
+ *> skipSpaces
+ *> fmap Data.Text.unpack (A.takeWhile $ not .
+ A.isHorizontalSpace)
+ a = skipSpacesAndString "(api:" . fmap unpack
+ $ A.takeWhile (/= '/')
+ p = A.string "/proto:"
+ *> fmap Data.Text.unpack (A.takeWhile (/= ')'))
+ <* A.takeTill A.isEndOfLine <* A.endOfLine
+ srcVersion = A.string "srcversion:"
+ *> AC.skipMany1 A.space
+ *> fmap unpack (A.takeTill A.isEndOfLine)
+ <* A.endOfLine
+ gh = A.string "GIT-hash:"
+ *> skipSpaces
+ *> A.takeWhile (not . A.isHorizontalSpace)
+ builder = skipSpacesAndString "build by" (
+ skipSpaces
+ *> A.takeTill A.isEndOfLine
+ <* A.endOfLine
+ )
+
+-- | The parser for a (multi-line) string representing a device
+deviceParser :: Parser DeviceInfo
+deviceParser = do
+ deviceNum <- skipSpaces *> A.decimal <* A.char ':'
+ cs <- skipSpacesAndString "cs:" connectionStateParser
+ if cs == Unconfigured then do
+ _ <- additionalEOL
+ return $ UnconfiguredDevice deviceNum
+ else do
+ ro <- skipSpaces *> skipRoleString *> localRemoteParser roleParser
+ ds <- skipSpacesAndString "ds:" $ localRemoteParser diskStateParser
+ replicProtocol <- A.space *> A.anyChar
+ io <- skipSpaces *> ioFlagsParser <* A.endOfLine
+ perfIndicators <- performanceIndicatorsParser
+ syncS <- conditionalSyncStatusParser cs
+ reS <- optional resyncParser
+ act <- optional actLogParser
+ _ <- additionalEOL
+ return $ DeviceInfo deviceNum cs ro ds replicProtocol io
+ perfIndicators syncS reS act
+
+ where conditionalSyncStatusParser SyncSource = Just <$> syncStatusParser
+ conditionalSyncStatusParser SyncTarget = Just <$> syncStatusParser
+ conditionalSyncStatusParser _ = pure Nothing
+ skipRoleString = A.string "ro:" <|> A.string "st:"
+ resyncParser = skipSpacesAndString "resync:"
+ additionalInfoParser
+ actLogParser = skipSpacesAndString "act_log:"
+ additionalInfoParser
+ additionalEOL = A.skipWhile A.isEndOfLine
+
+-- | The parser for the connection state
+connectionStateParser :: Parser ConnectionState
+connectionStateParser = standAlone
+ <|> disconnecting
+ <|> unconnected
+ <|> timeout
+ <|> brokenPipe
+ <|> networkFailure
+ <|> protocolError
+ <|> tearDown
+ <|> wfConnection
+ <|> wfReportParams
+ <|> connected
+ <|> startingSyncS
+ <|> startingSyncT
+ <|> wfBitMapS
+ <|> wfBitMapT
+ <|> wfSyncUUID
+ <|> syncSource
+ <|> syncTarget
+ <|> pausedSyncS
+ <|> pausedSyncT
+ <|> verifyS
+ <|> verifyT
+ <|> unconfigured
+ where standAlone = A.string "StandAlone" *> pure StandAlone
+ disconnecting = A.string "Disconnectiog" *> pure Disconnecting
+ unconnected = A.string "Unconnected" *> pure Unconnected
+ timeout = A.string "Timeout" *> pure Timeout
+ brokenPipe = A.string "BrokenPipe" *> pure BrokenPipe
+ networkFailure = A.string "NetworkFailure" *> pure NetworkFailure
+ protocolError = A.string "ProtocolError" *> pure ProtocolError
+ tearDown = A.string "TearDown" *> pure TearDown
+ wfConnection = A.string "WFConnection" *> pure WFConnection
+ wfReportParams = A.string "WFReportParams" *> pure WFReportParams
+ connected = A.string "Connected" *> pure Connected
+ startingSyncS = A.string "StartingSyncS" *> pure StartingSyncS
+ startingSyncT = A.string "StartingSyncT" *> pure StartingSyncT
+ wfBitMapS = A.string "WFBitMapS" *> pure WFBitMapS
+ wfBitMapT = A.string "WFBitMapT" *> pure WFBitMapT
+ wfSyncUUID = A.string "WFSyncUUID" *> pure WFSyncUUID
+ syncSource = A.string "SyncSource" *> pure SyncSource
+ syncTarget = A.string "SyncTarget" *> pure SyncTarget
+ pausedSyncS = A.string "PausedSyncS" *> pure PausedSyncS
+ pausedSyncT = A.string "PausedSyncT" *> pure PausedSyncT
+ verifyS = A.string "VerifyS" *> pure VerifyS
+ verifyT = A.string "VerifyT" *> pure VerifyT
+ unconfigured = A.string "Unconfigured" *> pure Unconfigured
+
+-- | Parser for recognizing strings describing two elements of the same type
+-- separated by a '/'. The first one is considered local, the second remote.
+localRemoteParser :: Parser a -> Parser (LocalRemote a)
+localRemoteParser parser = LocalRemote <$> parser
+ <*> (A.char '/' *> parser)
+
+-- | The parser for resource roles
+roleParser :: Parser Role
+roleParser = primary
+ <|> secondary
+ <|> unknown
+ where primary = A.string "Primary" *> pure Primary
+ secondary = A.string "Secondary" *> pure Secondary
+ unknown = A.string "Unknown" *> pure Unknown
+
+-- | The parser for disk states
+diskStateParser :: Parser DiskState
+diskStateParser = diskless
+ <|> attaching
+ <|> failed
+ <|> negotiating
+ <|> inconsistent
+ <|> outdated
+ <|> dUnknown
+ <|> consistent
+ <|> upToDate
+ where diskless = A.string "Diskless" *> pure Diskless
+ attaching = A.string "Attaching" *> pure Attaching
+ failed = A.string "Failed" *> pure Failed
+ negotiating = A.string "Negotiating" *> pure Negotiating
+ inconsistent = A.string "Inconsistent" *> pure Inconsistent
+ outdated = A.string "Outdated" *> pure Outdated
+ dUnknown = A.string "DUnknown" *> pure DUnknown
+ consistent = A.string "Consistent" *> pure Consistent
+ upToDate = A.string "UpToDate" *> pure UpToDate
+
+-- | The parser for I/O flags
+ioFlagsParser :: Parser String
+ioFlagsParser = some (A.notChar '\n')
+
+-- | The parser for performance indicators
+performanceIndicatorsParser :: Parser PerformanceIndicators
+performanceIndicatorsParser = PerformanceIndicators
+ <$> skipSpacesAndString "ns:" A.decimal
+ <*> skipSpacesAndString "nr:" A.decimal
+ <*> skipSpacesAndString "dw:" A.decimal
+ <*> skipSpacesAndString "dr:" A.decimal
+ <*> skipSpacesAndString "al:" A.decimal
+ <*> skipSpacesAndString "bm:" A.decimal
+ <*> skipSpacesAndString "lo:" A.decimal
+ <*> skipSpacesAndString "pe:" A.decimal
+ <*> skipSpacesAndString "ua:" A.decimal
+ <*> skipSpacesAndString "ap:" A.decimal
+ <*> optional (skipSpacesAndString "ep:" A.decimal)
+ <*> optional (skipSpacesAndString "wo:" A.anyChar)
+ <*> optional (skipSpacesAndString "oos:"
+ A.decimal)
+ <* skipSpaces <* A.endOfLine
+
+-- | The parser for the syncronization status
+syncStatusParser :: Parser SyncStatus
+syncStatusParser = do
+ _ <- statusBarParser
+ percent <- skipSpacesAndString "sync'ed:" (
+ skipSpaces
+ *> A.double
+ <* A.char '%'
+ )
+ partSyncSize <- skipSpaces
+ *> A.char '('
+ *> A.decimal
+ totSyncSize <- A.char '/'
+ *> A.decimal
+ <* A.char ')'
+ sizeUnit <- sizeUnitParser <* optional A.endOfLine
+ timeToEnd <- skipSpacesAndString "finish:" (
+ skipSpaces
+ *> timeParser
+ )
+ sp <- skipSpacesAndString "speed:" (
+ skipSpaces
+ *> commaDoubleParser
+ <* skipSpaces
+ <* A.char '('
+ <* commaDoubleParser
+ <* A.char ')'
+ )
+ w <- skipSpacesAndString "want:" (skipSpaces
+ *> (Just <$> commaDoubleParser)
+ )
+ <|> pure Nothing
+ sSizeUnit <- skipSpaces *> sizeUnitParser
+ sTimeUnit <- A.char '/' *> timeUnitParser
+ _ <- A.endOfLine
+ return $ SyncStatus percent partSyncSize totSyncSize
+ sizeUnit timeToEnd sp w sSizeUnit
+ sTimeUnit
+
+-- | The parser for recognizing (and discarding) the sync status bar
+statusBarParser :: Parser ()
+statusBarParser = skipSpaces
+ *> A.char '['
+ *> A.skipWhile (== '=')
+ *> A.skipWhile (== '>')
+ *> A.skipWhile (== '.')
+ *> A.char ']'
+ *> pure ()
+
+-- | The parser for recognizing data size units (only the ones actually found in
+-- DRBD files are implemented)
+sizeUnitParser :: Parser SizeUnit
+sizeUnitParser = kilobyte
+ <|> megabyte
+ where kilobyte = A.string "K" *> pure KiloByte
+ megabyte = A.string "M" *> pure MegaByte
+
+-- | The parser for recognizing time (hh:mm:ss)
+timeParser :: Parser Time
+timeParser = Time <$> h <*> m <*> s
+ where h = A.decimal
+ m = A.char ':' *> A.decimal
+ s = A.char ':' *> A.decimal
+
+-- | The parser for recognizing time units (only the ones actually found in DRBD
+-- files are implemented)
+timeUnitParser :: Parser TimeUnit
+timeUnitParser = second
+ where second = A.string "sec" *> pure Second
+
+-- | Haskell recognises '.' as the separator for the decimal part of numbers,
+-- but DRBD uses comma, so we need an ah-hoc parser.
+commaDoubleParser :: Parser Double
+commaDoubleParser = do
+ intPart <- A.decimal
+ _ <- A.char ','
+ decimalPart <- decimalPartParser []
+ pure $ fromIntegral (intPart::Integer) + decimalPart
+
+-- | Helper for commaDoubleParser for computing the decimal part
+decimalPartParser :: String -> Parser Double
+decimalPartParser acc = goOn <|> finished
+ where goOn = do
+ d <- A.digit
+ decimalPartParser $ acc ++ [d]
+ finished = let digits = read acc
+ numDecimalDigits = length acc
+ decimalPart = digits / (10 ^ numDecimalDigits)
+ in pure decimalPart
+
+-- | Parser for the additional information provided by DRBD <= 8.0
+additionalInfoParser::Parser AdditionalInfo
+additionalInfoParser = AdditionalInfo
+ <$> skipSpacesAndString "used:" A.decimal
+ <*> (A.char '/' *> A.decimal)
+ <*> skipSpacesAndString "hits:" A.decimal
+ <*> skipSpacesAndString "misses:" A.decimal
+ <*> skipSpacesAndString "starving:" A.decimal
+ <*> skipSpacesAndString "dirty:" A.decimal
+ <*> skipSpacesAndString "changed:" A.decimal
+ <* A.endOfLine
--
1.7.7.3