1 {-# LANGUAGE OverloadedStrings #-}
2 {-| Parser for the output of the @xm list --long@ command of Xen
7 Copyright (C) 2013 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 module Ganeti.Hypervisor.Xen.XmParser
32 import Control.Applicative
34 import qualified Data.Attoparsec.Combinator as AC
35 import qualified Data.Attoparsec.Text as A
36 import Data.Attoparsec.Text (Parser)
39 import Data.Text (unpack)
40 import qualified Data.Map as Map
42 import Ganeti.BasicTypes
43 import Ganeti.Hypervisor.Xen.Types
45 -- | A parser for parsing generic config files written in the (LISP-like)
46 -- format that is the output of the @xm list --long@ command.
47 -- This parser only takes care of the syntactic parse, but does not care
48 -- about the semantics.
49 -- Note: parsing the double requires checking for the next character in order
50 -- to prevent string like "9a" to be recognized as the number 9.
51 lispConfigParser :: Parser LispConfig
59 where listConfigP = LCList <$> (A.char '(' *> liftA2 (++)
61 (((:[]) <$> finalP) <|> (rparen *> pure [])))
62 doubleP = LCDouble <$> A.double <* A.skipSpace <* A.endOfInput
63 innerDoubleP = LCDouble <$> A.double
64 stringP = LCString . unpack <$> A.takeWhile1 (not . (\c -> isSpace c
66 wspace = AC.many1 A.space
67 rparen = A.skipSpace *> A.char ')'
68 finalP = listConfigP <* rparen
69 <|> innerDoubleP <* rparen
71 middleP = listConfigP <* wspace
72 <|> innerDoubleP <* wspace
76 -- | Find a configuration having the given string as its first element,
77 -- from a list of configurations.
78 findConf :: String -> [LispConfig] -> Result LispConfig
79 findConf key configs =
80 case find (isNamed key) configs of
82 _ -> Bad "Configuration not found"
84 -- | Get the value of of a configuration having the given string as its
86 -- The value is the content of the configuration, discarding the name itself.
87 getValue :: (FromLispConfig a) => String -> [LispConfig] -> Result a
88 getValue key configs = findConf key configs >>= fromLispConfig
90 -- | Extract the values of a configuration containing a list of them.
91 extractValues :: LispConfig -> Result [LispConfig]
92 extractValues c = tail `fmap` fromLispConfig c
94 -- | Verify whether the given configuration has a certain name or not.fmap
95 -- The name of a configuration is its first parameter, if it is a string.
96 isNamed :: String -> LispConfig -> Bool
97 isNamed key (LCList (LCString x:_)) = x == key
100 -- | Parser for recognising the current state of a Xen domain.
101 parseState :: String -> ActualState
104 "r-----" -> ActualRunning
105 "-b----" -> ActualBlocked
106 "--p---" -> ActualPaused
107 "---s--" -> ActualShutdown
108 "----c-" -> ActualCrashed
109 "-----d" -> ActualDying
112 -- | Extract the configuration data of a Xen domain from a generic LispConfig
113 -- data structure. Fail if the LispConfig does not represent a domain.
114 getDomainConfig :: LispConfig -> Result Domain
115 getDomainConfig configData = do
117 if isNamed "domain" configData
118 then extractValues configData
119 else Bad $ "Not a domain configuration: " ++ show configData
120 domid <- getValue "domid" domainConf
121 name <- getValue "name" domainConf
122 cpuTime <- getValue "cpu_time" domainConf
123 state <- getValue "state" domainConf
124 let actualState = parseState state
125 return $ Domain domid name cpuTime actualState Nothing
127 -- | A parser for parsing the output of the @xm list --long@ command.
128 -- It adds the semantic layer on top of lispConfigParser.
129 -- It returns a map of domains, with their name as the key.
130 -- FIXME: This is efficient under the assumption that only a few fields of the
131 -- domain configuration are actually needed. If many of them are required, a
132 -- parser able to directly extract the domain config would actually be better.
133 xmListParser :: Parser (Map.Map String Domain)
135 configs <- lispConfigParser `AC.manyTill` A.endOfInput
136 let domains = map getDomainConfig configs
137 foldResult m (Ok val) = Ok $ Map.insert (domName val) val m
138 foldResult _ (Bad msg) = Bad msg
139 case foldM foldResult Map.empty domains of
143 -- | A parser for parsing the output of the @xm uptime@ command.
144 xmUptimeParser :: Parser (Map.Map Int UptimeInfo)
147 uptimes <- uptimeLineParser `AC.manyTill` A.endOfInput
148 return $ Map.fromList [(uInfoID u, u) | u <- uptimes]
149 where headerParser = A.string "Name" <* A.skipSpace <* A.string "ID"
150 <* A.skipSpace <* A.string "Uptime" <* A.skipSpace
152 -- | A helper for parsing a single line of the @xm uptime@ output.
153 uptimeLineParser :: Parser UptimeInfo
154 uptimeLineParser = do
155 name <- A.takeTill isSpace <* A.skipSpace
156 idNum <- A.decimal <* A.skipSpace
157 uptime <- A.takeTill (`elem` "\n\r") <* A.skipSpace
158 return . UptimeInfo (unpack name) idNum $ unpack uptime