1 {-# LANGUAGE TemplateHaskell #-}
3 {-| Implementation of the Ganeti Query2 language.
9 Copyright (C) 2012 Google Inc.
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34 , QueryFieldsResult(..)
41 import Control.Applicative
42 import Data.Ratio (numerator, denominator)
43 import Text.JSON.Pretty (pp_value)
44 import Text.JSON.Types
47 import qualified Ganeti.Constants as C
50 -- * THH declarations, that require ordering.
52 -- | Status of a query field.
53 $(declareIADT "ResultStatus"
54 [ ("RSNormal", 'C.rsNormal )
55 , ("RSUnknown", 'C.rsUnknown )
56 , ("RSNoData", 'C.rsNodata )
57 , ("RSUnavail", 'C.rsUnavail )
58 , ("RSOffline", 'C.rsOffline )
60 $(makeJSONInstance ''ResultStatus)
62 -- | Check that ResultStatus is success or fail with descriptive
64 checkRS :: (Monad m) => ResultStatus -> a -> m a
65 checkRS RSNormal val = return val
66 checkRS RSUnknown _ = fail "Unknown field"
67 checkRS RSNoData _ = fail "No data for a field"
68 checkRS RSUnavail _ = fail "Ganeti reports unavailable data"
69 checkRS RSOffline _ = fail "Ganeti reports resource as offline"
71 -- | Type of a query field.
72 $(declareSADT "FieldType"
73 [ ("QFTUnknown", 'C.qftUnknown )
74 , ("QFTText", 'C.qftText )
75 , ("QFTBool", 'C.qftBool )
76 , ("QFTNumber", 'C.qftNumber )
77 , ("QFTUnit", 'C.qftUnit )
78 , ("QFTTimestamp", 'C.qftTimestamp )
79 , ("QFTOther", 'C.qftOther )
81 $(makeJSONInstance ''FieldType)
83 -- | Supported items on which Qlang works.
84 $(declareSADT "ItemType"
85 [ ("QRCluster", 'C.qrCluster )
86 , ("QRInstance", 'C.qrInstance )
87 , ("QRNode", 'C.qrNode )
88 , ("QRLock", 'C.qrLock )
89 , ("QRGroup", 'C.qrGroup )
91 , ("QRJob", 'C.qrJob )
92 , ("QRExport", 'C.qrExport )
94 $(makeJSONInstance ''ItemType)
96 -- * Main Qlang queries and responses.
99 data Query = Query ItemType Fields (Maybe Filter)
102 data QueryResult = QueryResult [ FieldDefinition ] [ ResultEntry ]
104 -- | Query2 Fields query.
105 -- (to get supported fields names, descriptions, and types)
106 data QueryFields = QueryFields ItemType Fields
108 -- | Query2 Fields result.
109 data QueryFieldsResult = QueryFieldsResult [ FieldDefinition ]
111 -- * Sub data types for query2 queries and responses.
113 -- | List of requested fields.
114 type Fields = [ String ]
116 -- | Query2 filter expression.
118 = EmptyFilter -- ^ No filter at all
119 | AndFilter [ Filter ] -- ^ & [<expression>, ...]
120 | OrFilter [ Filter ] -- ^ | [<expression>, ...]
121 | NotFilter Filter -- ^ ! <expression>
122 | TrueFilter FilterField -- ^ ? <field>
123 | EQFilter FilterField FilterValue -- ^ (=|!=) <field> <value>
124 | LTFilter FilterField FilterValue -- ^ < <field> <value>
125 | GTFilter FilterField FilterValue -- ^ > <field> <value>
126 | LEFilter FilterField FilterValue -- ^ <= <field> <value>
127 | GEFilter FilterField FilterValue -- ^ >= <field> <value>
128 | RegexpFilter FilterField FilterRegexp -- ^ =~ <field> <regexp>
129 | ContainsFilter FilterField FilterValue -- ^ =[] <list-field> <value>
130 deriving (Show, Read, Eq)
132 -- | Serialiser for the 'Filter' data type.
133 showFilter :: Filter -> JSValue
134 showFilter (EmptyFilter) = JSNull
135 showFilter (AndFilter exprs) =
136 JSArray $ (showJSON C.qlangOpAnd):(map showJSON exprs)
137 showFilter (OrFilter exprs) =
138 JSArray $ (showJSON C.qlangOpOr):(map showJSON exprs)
139 showFilter (NotFilter flt) =
140 JSArray [showJSON C.qlangOpNot, showJSON flt]
141 showFilter (TrueFilter field) =
142 JSArray [showJSON C.qlangOpTrue, showJSON field]
143 showFilter (EQFilter field value) =
144 JSArray [showJSON C.qlangOpEqual, showJSON field, showJSON value]
145 showFilter (LTFilter field value) =
146 JSArray [showJSON C.qlangOpLt, showJSON field, showJSON value]
147 showFilter (GTFilter field value) =
148 JSArray [showJSON C.qlangOpGt, showJSON field, showJSON value]
149 showFilter (LEFilter field value) =
150 JSArray [showJSON C.qlangOpLe, showJSON field, showJSON value]
151 showFilter (GEFilter field value) =
152 JSArray [showJSON C.qlangOpGe, showJSON field, showJSON value]
153 showFilter (RegexpFilter field regexp) =
154 JSArray [showJSON C.qlangOpRegexp, showJSON field, showJSON regexp]
155 showFilter (ContainsFilter field value) =
156 JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
158 -- | Deserializer for the 'Filter' data type.
159 readFilter :: JSValue -> Result Filter
160 readFilter JSNull = Ok EmptyFilter
161 readFilter (JSArray (JSString op:args)) =
162 readFilterArray (fromJSString op) args
164 Error $ "Cannot deserialise filter: expected array [string, args], got " ++
167 -- | Helper to deserialise an array corresponding to a single filter
168 -- and return the built filter. Note this looks generic but is (at
169 -- least currently) only used for the NotFilter.
170 readFilterArg :: (Filter -> Filter) -- ^ Constructor
171 -> [JSValue] -- ^ Single argument
173 readFilterArg constr [flt] = constr <$> readJSON flt
174 readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]\
175 \ but got " ++ show (pp_value (showJSON v))
177 -- | Helper to deserialise an array corresponding to a single field
178 -- and return the built filter.
179 readFilterField :: (FilterField -> Filter) -- ^ Constructor
180 -> [JSValue] -- ^ Single argument
182 readFilterField constr [field] = constr <$> readJSON field
183 readFilterField _ v = Error $ "Cannot deserialise field, expected [fieldname]\
184 \ but got " ++ show (pp_value (showJSON v))
186 -- | Helper to deserialise an array corresponding to a field and
187 -- value, returning the built filter.
188 readFilterFieldValue :: (JSON a) =>
189 (FilterField -> a -> Filter) -- ^ Constructor
190 -> [JSValue] -- ^ Arguments array
192 readFilterFieldValue constr [field, value] =
193 constr <$> readJSON field <*> readJSON value
194 readFilterFieldValue _ v =
195 Error $ "Cannot deserialise field/value pair, expected [fieldname, value]\
196 \ but got " ++ show (pp_value (showJSON v))
198 -- | Inner deserialiser for 'Filter'.
199 readFilterArray :: String -> [JSValue] -> Result Filter
200 readFilterArray op args
201 | op == C.qlangOpAnd = AndFilter <$> mapM readJSON args
202 | op == C.qlangOpOr = OrFilter <$> mapM readJSON args
203 | op == C.qlangOpNot = readFilterArg NotFilter args
204 | op == C.qlangOpTrue = readFilterField TrueFilter args
205 | op == C.qlangOpEqual = readFilterFieldValue EQFilter args
206 | op == C.qlangOpLt = readFilterFieldValue LTFilter args
207 | op == C.qlangOpGt = readFilterFieldValue GTFilter args
208 | op == C.qlangOpLe = readFilterFieldValue LEFilter args
209 | op == C.qlangOpGe = readFilterFieldValue GEFilter args
210 | op == C.qlangOpRegexp = readFilterFieldValue RegexpFilter args
211 | op == C.qlangOpContains = readFilterFieldValue ContainsFilter args
212 | otherwise = Error $ "Unknown filter operand '" ++ op ++ "'"
214 instance JSON Filter where
215 showJSON = showFilter
216 readJSON = readFilter
218 -- | Field name to filter on.
219 type FilterField = String
221 -- | Value to compare the field value to, for filtering purposes.
222 data FilterValue = QuotedString String
223 | NumericValue Integer
224 deriving (Read, Show, Eq)
226 -- | Serialiser for 'FilterValue'. The Python code just sends this to
227 -- JSON as-is, so we'll do the same.
228 showFilterValue :: FilterValue -> JSValue
229 showFilterValue (QuotedString str) = showJSON str
230 showFilterValue (NumericValue val) = showJSON val
232 -- | Decoder for 'FilterValue'. We have to see what it contains, since
233 -- the context doesn't give us hints on what to expect.
234 readFilterValue :: JSValue -> Result FilterValue
235 readFilterValue (JSString a) = Ok . QuotedString $ fromJSString a
236 readFilterValue (JSRational _ x) =
237 if denominator x /= 1
238 then Error $ "Cannot deserialise numeric filter value,\
239 \ expecting integral but\
240 \ got a fractional value: " ++ show x
241 else Ok . NumericValue $ numerator x
242 readFilterValue v = Error $ "Cannot deserialise filter value, expecting\
243 \ string or integer, got " ++ show (pp_value v)
245 instance JSON FilterValue where
246 showJSON = showFilterValue
247 readJSON = readFilterValue
249 -- | Regexp to apply to the filter value, for filteriong purposes.
250 type FilterRegexp = String
252 -- | Definition of a field.
253 data FieldDefinition = FieldDefinition FieldName FieldTitle FieldType FieldDoc
255 -- | Name of a field.
256 type FieldName = String
257 -- | Title of a field, when represented in tabular format.
258 type FieldTitle = String
259 -- | Human redable description of a field.
260 type FieldDoc = String
262 --- | Single field entry result.
263 data ResultEntry = ResultEntry ResultStatus (Maybe ResultValue)
265 -- | Value of a field, in json encoding.
266 -- (its type will be depending on ResultStatus and FieldType)
267 type ResultValue = JSValue