module Ganeti.Block.Drbd.Parser (drbdStatusParser, commaIntParser) where
import Control.Applicative ((<*>), (*>), (<*), (<$>), (<|>), pure)
import qualified Data.Attoparsec.Text as A
import qualified Data.Attoparsec.Combinator as AC
import Data.Attoparsec.Text (Parser)
import Data.List
import Data.Maybe
import Data.Text (Text, unpack)
import Ganeti.Block.Drbd.Types
skipSpaces :: Parser ()
skipSpaces = A.skipWhile A.isHorizontalSpace
skipSpacesAndString :: Text -> Parser a -> Parser a
skipSpacesAndString s parser =
skipSpaces
*> A.string s
*> parser
isBadEndOfLine :: Char -> Bool
isBadEndOfLine c = (c == '\0') || A.isEndOfLine c
optional :: Parser a -> Parser (Maybe a)
optional parser = (Just <$> parser) <|> pure Nothing
drbdStatusParser :: [DrbdInstMinor] -> Parser DRBDStatus
drbdStatusParser instMinor =
DRBDStatus <$> versionInfoParser
<*> deviceParser instMinor `AC.manyTill` A.endOfInput
<* A.endOfInput
versionInfoParser :: Parser VersionInfo
versionInfoParser = do
versionF <- optional versionP
apiF <- optional apiP
protoF <- optional protoP
srcVersionF <- optional srcVersion
ghF <- fmap unpack <$> optional gh
builderF <- fmap unpack <$> optional builder
if isNothing versionF
&& isNothing apiF
&& isNothing protoF
&& isNothing srcVersionF
&& isNothing ghF
&& isNothing builderF
then fail "versionInfo"
else pure $ VersionInfo versionF apiF protoF srcVersionF ghF builderF
where versionP =
A.string "version:"
*> skipSpaces
*> fmap unpack (A.takeWhile $ not . A.isHorizontalSpace)
apiP =
skipSpacesAndString "(api:" . fmap unpack $ A.takeWhile (/= '/')
protoP =
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
deviceParser :: [DrbdInstMinor] -> Parser DeviceInfo
deviceParser instMinor = do
deviceNum <- skipSpaces *> A.decimal <* A.char ':'
cs <- skipSpacesAndString "cs:" connStateParser
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.skipWhile isBadEndOfLine
pIndicators <- perfIndicatorsParser
syncS <- conditionalSyncStatusParser cs
reS <- optional resyncParser
act <- optional actLogParser
_ <- additionalEOL
let inst = find ((deviceNum ==) . dimMinor) instMinor
iName = fmap dimInstName inst
return $ DeviceInfo deviceNum cs ro ds replicProtocol io pIndicators
syncS reS act iName
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
connStateParser :: Parser ConnState
connStateParser =
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
localRemoteParser :: Parser a -> Parser (LocalRemote a)
localRemoteParser parser = LocalRemote <$> parser <*> (A.char '/' *> parser)
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
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
ioFlagsParser :: Parser String
ioFlagsParser = fmap unpack . A.takeWhile $ not . isBadEndOfLine
perfIndicatorsParser :: Parser PerfIndicators
perfIndicatorsParser =
PerfIndicators
<$> 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
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
*> commaIntParser
<* skipSpaces
<* A.char '('
<* commaIntParser
<* A.char ')'
w <- skipSpacesAndString "want:" (
skipSpaces
*> (Just <$> commaIntParser)
)
<|> pure Nothing
sSizeUnit <- skipSpaces *> sizeUnitParser
sTimeUnit <- A.char '/' *> timeUnitParser
_ <- A.endOfLine
return $
SyncStatus percent partSyncSize totSyncSize sizeUnit timeToEnd sp w
sSizeUnit sTimeUnit
statusBarParser :: Parser ()
statusBarParser =
skipSpaces
*> A.char '['
*> A.skipWhile (== '=')
*> A.skipWhile (== '>')
*> A.skipWhile (== '.')
*> A.char ']'
*> pure ()
sizeUnitParser :: Parser SizeUnit
sizeUnitParser =
kilobyte
<|> megabyte
where kilobyte = A.string "K" *> pure KiloByte
megabyte = A.string "M" *> pure MegaByte
timeParser :: Parser Time
timeParser = Time <$> h <*> m <*> s
where h = A.decimal :: Parser Int
m = A.char ':' *> A.decimal :: Parser Int
s = A.char ':' *> A.decimal :: Parser Int
timeUnitParser :: Parser TimeUnit
timeUnitParser = second
where second = A.string "sec" *> pure Second
commaIntParser :: Parser Int
commaIntParser = do
first <-
AC.count 3 A.digit <|> AC.count 2 A.digit <|> AC.count 1 A.digit
allDigits <- commaIntHelper (read first)
pure allDigits
commaIntHelper :: Int -> Parser Int
commaIntHelper acc = nextTriplet <|> end
where nextTriplet = do
_ <- A.char ','
triplet <- AC.count 3 A.digit
commaIntHelper $ acc * 1000 + (read triplet :: Int)
end = pure acc :: Parser Int
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