Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Block / Drbd / Parser.hs @ eb62691c

History | View | Annotate | Download (12.8 kB)

1
{-# LANGUAGE OverloadedStrings #-}
2
{-| DRBD proc file parser
3

    
4
This module holds the definition of the parser that extracts status
5
information from the DRBD proc file.
6

    
7
-}
8
{-
9

    
10
Copyright (C) 2012 Google Inc.
11

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28
module Ganeti.Block.Drbd.Parser (drbdStatusParser, commaIntParser) where
29

    
30
import Control.Applicative ((<*>), (*>), (<*), (<$>), (<|>), pure)
31
import qualified Data.Attoparsec.Text as A
32
import qualified Data.Attoparsec.Combinator as AC
33
import Data.Attoparsec.Text (Parser)
34
import Data.List
35
import Data.Maybe
36
import Data.Text (Text, unpack)
37

    
38
import Ganeti.Block.Drbd.Types
39

    
40
-- | Our own space-skipping function, because A.skipSpace also skips
41
-- newline characters. It skips ZERO or more spaces, so it does not
42
-- fail if there are no spaces.
43
skipSpaces :: Parser ()
44
skipSpaces = A.skipWhile A.isHorizontalSpace
45

    
46
-- | Skips spaces and the given string, then executes a parser and
47
-- returns its result.
48
skipSpacesAndString :: Text -> Parser a -> Parser a
49
skipSpacesAndString s parser =
50
  skipSpaces
51
  *> A.string s
52
  *> parser
53

    
54
-- | Predicate verifying (potentially bad) end of lines
55
isBadEndOfLine :: Char -> Bool
56
isBadEndOfLine c = (c == '\0') || A.isEndOfLine c
57

    
58
-- | Takes a parser and returns it with the content wrapped in a Maybe
59
-- object. The resulting parser never fails, but contains Nothing if
60
-- it couldn't properly parse the string.
61
optional :: Parser a -> Parser (Maybe a)
62
optional parser = (Just <$> parser) <|> pure Nothing
63

    
64
-- | The parser for a whole DRBD status file.
65
drbdStatusParser :: [DrbdInstMinor] -> Parser DRBDStatus
66
drbdStatusParser instMinor =
67
  DRBDStatus <$> versionInfoParser
68
             <*> deviceParser instMinor `AC.manyTill` A.endOfInput
69
             <* A.endOfInput
70

    
71
-- | The parser for the version information lines.
72
versionInfoParser :: Parser VersionInfo
73
versionInfoParser = do
74
  versionF <- optional versionP
75
  apiF <- optional apiP
76
  protoF <- optional protoP
77
  srcVersionF <- optional srcVersion
78
  ghF <- fmap unpack <$> optional gh
79
  builderF <- fmap unpack <$> optional builder
80
  if   isNothing versionF
81
    && isNothing apiF
82
    && isNothing protoF
83
    && isNothing srcVersionF
84
    && isNothing ghF
85
    && isNothing builderF
86
    then fail "versionInfo"
87
    else pure $ VersionInfo versionF apiF protoF srcVersionF ghF builderF
88

    
89
    where versionP =
90
            A.string "version:"
91
            *> skipSpaces
92
            *> fmap unpack (A.takeWhile $ not . A.isHorizontalSpace)
93
          apiP =
94
            skipSpacesAndString "(api:" . fmap unpack $ A.takeWhile (/= '/')
95
          protoP =
96
            A.string "/proto:"
97
            *> fmap Data.Text.unpack (A.takeWhile (/= ')'))
98
            <* A.takeTill A.isEndOfLine <* A.endOfLine
99
          srcVersion =
100
            A.string "srcversion:"
101
            *> AC.skipMany1 A.space
102
            *> fmap unpack (A.takeTill A.isEndOfLine)
103
            <* A.endOfLine
104
          gh =
105
            A.string "GIT-hash:"
106
            *> skipSpaces
107
            *> A.takeWhile (not . A.isHorizontalSpace)
108
          builder =
109
            skipSpacesAndString "build by" $
110
              skipSpaces
111
              *> A.takeTill A.isEndOfLine
112
              <* A.endOfLine
113

    
114
-- | The parser for a (multi-line) string representing a device.
115
deviceParser :: [DrbdInstMinor] -> Parser DeviceInfo
116
deviceParser instMinor = do
117
  deviceNum <- skipSpaces *> A.decimal <* A.char ':'
118
  cs <- skipSpacesAndString "cs:" connStateParser
119
  if cs == Unconfigured
120
    then do
121
      _ <- additionalEOL
122
      return $ UnconfiguredDevice deviceNum
123
    else do
124
      ro <- skipSpaces *> skipRoleString *> localRemoteParser roleParser
125
      ds <- skipSpacesAndString "ds:" $ localRemoteParser diskStateParser
126
      replicProtocol <- A.space *> A.anyChar
127
      io <- skipSpaces *> ioFlagsParser <* A.skipWhile isBadEndOfLine
128
      pIndicators <- perfIndicatorsParser
129
      syncS <- conditionalSyncStatusParser cs
130
      reS <- optional resyncParser
131
      act <- optional actLogParser
132
      _ <- additionalEOL
133
      let inst = find ((deviceNum ==) . dimMinor) instMinor
134
          iName = fmap dimInstName inst
135
      return $ DeviceInfo deviceNum cs ro ds replicProtocol io pIndicators
136
                          syncS reS act iName
137

    
138
    where conditionalSyncStatusParser SyncSource = Just <$> syncStatusParser
139
          conditionalSyncStatusParser SyncTarget = Just <$> syncStatusParser
140
          conditionalSyncStatusParser _ = pure Nothing
141
          skipRoleString = A.string "ro:" <|> A.string "st:"
142
          resyncParser = skipSpacesAndString "resync:" additionalInfoParser
143
          actLogParser = skipSpacesAndString "act_log:" additionalInfoParser
144
          additionalEOL = A.skipWhile A.isEndOfLine
145

    
146
-- | The parser for the connection state.
147
connStateParser :: Parser ConnState
148
connStateParser =
149
  standAlone
150
  <|> disconnecting
151
  <|> unconnected
152
  <|> timeout
153
  <|> brokenPipe
154
  <|> networkFailure
155
  <|> protocolError
156
  <|> tearDown
157
  <|> wfConnection
158
  <|> wfReportParams
159
  <|> connected
160
  <|> startingSyncS
161
  <|> startingSyncT
162
  <|> wfBitMapS
163
  <|> wfBitMapT
164
  <|> wfSyncUUID
165
  <|> syncSource
166
  <|> syncTarget
167
  <|> pausedSyncS
168
  <|> pausedSyncT
169
  <|> verifyS
170
  <|> verifyT
171
  <|> unconfigured
172
    where standAlone     = A.string "StandAlone"     *> pure StandAlone
173
          disconnecting  = A.string "Disconnectiog"  *> pure Disconnecting
174
          unconnected    = A.string "Unconnected"    *> pure Unconnected
175
          timeout        = A.string "Timeout"        *> pure Timeout
176
          brokenPipe     = A.string "BrokenPipe"     *> pure BrokenPipe
177
          networkFailure = A.string "NetworkFailure" *> pure NetworkFailure
178
          protocolError  = A.string "ProtocolError"  *> pure ProtocolError
179
          tearDown       = A.string "TearDown"       *> pure TearDown
180
          wfConnection   = A.string "WFConnection"   *> pure WFConnection
181
          wfReportParams = A.string "WFReportParams" *> pure WFReportParams
182
          connected      = A.string "Connected"      *> pure Connected
183
          startingSyncS  = A.string "StartingSyncS"  *> pure StartingSyncS
184
          startingSyncT  = A.string "StartingSyncT"  *> pure StartingSyncT
185
          wfBitMapS      = A.string "WFBitMapS"      *> pure WFBitMapS
186
          wfBitMapT      = A.string "WFBitMapT"      *> pure WFBitMapT
187
          wfSyncUUID     = A.string "WFSyncUUID"     *> pure WFSyncUUID
188
          syncSource     = A.string "SyncSource"     *> pure SyncSource
189
          syncTarget     = A.string "SyncTarget"     *> pure SyncTarget
190
          pausedSyncS    = A.string "PausedSyncS"    *> pure PausedSyncS
191
          pausedSyncT    = A.string "PausedSyncT"    *> pure PausedSyncT
192
          verifyS        = A.string "VerifyS"        *> pure VerifyS
193
          verifyT        = A.string "VerifyT"        *> pure VerifyT
194
          unconfigured   = A.string "Unconfigured"   *> pure Unconfigured
195

    
196
-- | Parser for recognizing strings describing two elements of the
197
-- same type separated by a '/'. The first one is considered local,
198
-- the second remote.
199
localRemoteParser :: Parser a -> Parser (LocalRemote a)
200
localRemoteParser parser = LocalRemote <$> parser <*> (A.char '/' *> parser)
201

    
202
-- | The parser for resource roles.
203
roleParser :: Parser Role
204
roleParser =
205
  primary
206
  <|> secondary
207
  <|> unknown
208
    where primary   = A.string "Primary"   *> pure Primary
209
          secondary = A.string "Secondary" *> pure Secondary
210
          unknown   = A.string "Unknown"   *> pure Unknown
211

    
212
-- | The parser for disk states.
213
diskStateParser :: Parser DiskState
214
diskStateParser =
215
  diskless
216
  <|> attaching
217
  <|> failed
218
  <|> negotiating
219
  <|> inconsistent
220
  <|> outdated
221
  <|> dUnknown
222
  <|> consistent
223
  <|> upToDate
224
    where diskless     = A.string "Diskless"     *> pure Diskless
225
          attaching    = A.string "Attaching"    *> pure Attaching
226
          failed       = A.string "Failed"       *> pure Failed
227
          negotiating  = A.string "Negotiating"  *> pure Negotiating
228
          inconsistent = A.string "Inconsistent" *> pure Inconsistent
229
          outdated     = A.string "Outdated"     *> pure Outdated
230
          dUnknown     = A.string "DUnknown"     *> pure DUnknown
231
          consistent   = A.string "Consistent"   *> pure Consistent
232
          upToDate     = A.string "UpToDate"     *> pure UpToDate
233

    
234
-- | The parser for I/O flags.
235
ioFlagsParser :: Parser String
236
ioFlagsParser = fmap unpack . A.takeWhile $ not . isBadEndOfLine
237

    
238
-- | The parser for performance indicators.
239
perfIndicatorsParser :: Parser PerfIndicators
240
perfIndicatorsParser =
241
  PerfIndicators
242
    <$> skipSpacesAndString "ns:" A.decimal
243
    <*> skipSpacesAndString "nr:" A.decimal
244
    <*> skipSpacesAndString "dw:" A.decimal
245
    <*> skipSpacesAndString "dr:" A.decimal
246
    <*> skipSpacesAndString "al:" A.decimal
247
    <*> skipSpacesAndString "bm:" A.decimal
248
    <*> skipSpacesAndString "lo:" A.decimal
249
    <*> skipSpacesAndString "pe:" A.decimal
250
    <*> skipSpacesAndString "ua:" A.decimal
251
    <*> skipSpacesAndString "ap:" A.decimal
252
    <*> optional (skipSpacesAndString "ep:" A.decimal)
253
    <*> optional (skipSpacesAndString "wo:" A.anyChar)
254
    <*> optional (skipSpacesAndString "oos:" A.decimal)
255
    <* skipSpaces <* A.endOfLine
256

    
257
-- | The parser for the syncronization status.
258
syncStatusParser :: Parser SyncStatus
259
syncStatusParser = do
260
  _ <- statusBarParser
261
  percent <-
262
    skipSpacesAndString "sync'ed:" $ skipSpaces *> A.double <* A.char '%'
263
  partSyncSize <- skipSpaces *> A.char '(' *> A.decimal
264
  totSyncSize <- A.char '/' *> A.decimal <* A.char ')'
265
  sizeUnit <- sizeUnitParser <* optional A.endOfLine
266
  timeToEnd <- skipSpacesAndString "finish:" $ skipSpaces *> timeParser
267
  sp <-
268
    skipSpacesAndString "speed:" $
269
      skipSpaces
270
      *> commaIntParser
271
      <* skipSpaces
272
      <* A.char '('
273
      <* commaIntParser
274
      <* A.char ')'
275
  w <- skipSpacesAndString "want:" (
276
         skipSpaces
277
         *> (Just <$> commaIntParser)
278
       )
279
       <|> pure Nothing
280
  sSizeUnit <- skipSpaces *> sizeUnitParser
281
  sTimeUnit <- A.char '/' *> timeUnitParser
282
  _ <- A.endOfLine
283
  return $
284
    SyncStatus percent partSyncSize totSyncSize sizeUnit timeToEnd sp w
285
      sSizeUnit sTimeUnit
286

    
287
-- | The parser for recognizing (and discarding) the sync status bar.
288
statusBarParser :: Parser ()
289
statusBarParser =
290
  skipSpaces
291
  *> A.char '['
292
  *> A.skipWhile (== '=')
293
  *> A.skipWhile (== '>')
294
  *> A.skipWhile (== '.')
295
  *> A.char ']'
296
  *> pure ()
297

    
298
-- | The parser for recognizing data size units (only the ones
299
-- actually found in DRBD files are implemented).
300
sizeUnitParser :: Parser SizeUnit
301
sizeUnitParser =
302
  kilobyte
303
  <|> megabyte
304
    where kilobyte = A.string "K" *> pure KiloByte
305
          megabyte = A.string "M" *> pure MegaByte
306

    
307
-- | The parser for recognizing time (hh:mm:ss).
308
timeParser :: Parser Time
309
timeParser = Time <$> h <*> m <*> s
310
  where h = A.decimal :: Parser Int
311
        m = A.char ':' *> A.decimal :: Parser Int
312
        s = A.char ':' *> A.decimal :: Parser Int
313

    
314
-- | The parser for recognizing time units (only the ones actually
315
-- found in DRBD files are implemented).
316
timeUnitParser :: Parser TimeUnit
317
timeUnitParser = second
318
  where second = A.string "sec" *> pure Second
319

    
320
-- | Haskell does not recognise ',' as the thousands separator every 3
321
-- digits but DRBD uses it, so we need an ah-hoc parser.
322
-- If a number beginning with more than 3 digits without a comma is
323
-- parsed, only the first 3 digits are considered to be valid, the rest
324
-- is not consumed, and left for further parsing.
325
commaIntParser :: Parser Int
326
commaIntParser = do
327
  first <-
328
    AC.count 3 A.digit <|> AC.count 2 A.digit <|> AC.count 1 A.digit
329
  allDigits <- commaIntHelper (read first)
330
  pure allDigits
331

    
332
-- | Helper (triplet parser) for the commaIntParser
333
commaIntHelper :: Int -> Parser Int
334
commaIntHelper acc = nextTriplet <|> end
335
  where nextTriplet = do
336
          _ <- A.char ','
337
          triplet <- AC.count 3 A.digit
338
          commaIntHelper $ acc * 1000 + (read triplet :: Int)
339
        end = pure acc :: Parser Int
340

    
341
-- | Parser for the additional information provided by DRBD <= 8.0.
342
additionalInfoParser::Parser AdditionalInfo
343
additionalInfoParser = AdditionalInfo
344
  <$> skipSpacesAndString "used:" A.decimal
345
  <*> (A.char '/' *> A.decimal)
346
  <*> skipSpacesAndString "hits:" A.decimal
347
  <*> skipSpacesAndString "misses:" A.decimal
348
  <*> skipSpacesAndString "starving:" A.decimal
349
  <*> skipSpacesAndString "dirty:" A.decimal
350
  <*> skipSpacesAndString "changed:" A.decimal
351
  <* A.endOfLine