Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / Qlang.hs @ 05ac718f

History | View | Annotate | Download (11.9 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Implementation of the Ganeti Query2 language.
4

    
5
 -}
6

    
7
{-
8

    
9
Copyright (C) 2012 Google Inc.
10

    
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.
15

    
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.
20

    
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
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Ganeti.Qlang
29
    ( Filter(..)
30
    , FilterField
31
    , FilterValue(..)
32
    , Fields
33
    , Query(..)
34
    , QueryResult(..)
35
    , QueryFields(..)
36
    , QueryFieldsResult(..)
37
    , FieldName
38
    , FieldTitle
39
    , FieldType(..)
40
    , FieldDoc
41
    , FieldDefinition(..)
42
    , ResultEntry(..)
43
    , ResultStatus(..)
44
    , ResultValue
45
    , ItemType(..)
46
    , checkRS
47
    ) where
48

    
49
import Control.Applicative
50
import Data.Foldable
51
import Data.Traversable (Traversable, traverse, fmapDefault, foldMapDefault)
52
import Data.Ratio (numerator, denominator)
53
import Text.JSON.Pretty (pp_value)
54
import Text.JSON.Types
55
import Text.JSON
56

    
57
import qualified Ganeti.Constants as C
58
import Ganeti.THH
59
import Ganeti.HTools.JSON
60

    
61
-- * THH declarations, that require ordering.
62

    
63
-- | Status of a query field.
64
$(declareIADT "ResultStatus"
65
  [ ("RSNormal",  'C.rsNormal )
66
  , ("RSUnknown", 'C.rsUnknown )
67
  , ("RSNoData",  'C.rsNodata )
68
  , ("RSUnavail", 'C.rsUnavail )
69
  , ("RSOffline", 'C.rsOffline )
70
  ])
71
$(makeJSONInstance ''ResultStatus)
72

    
73
-- | Check that ResultStatus is success or fail with descriptive
74
-- message.
75
checkRS :: (Monad m) => ResultStatus -> a -> m a
76
checkRS RSNormal val = return val
77
checkRS RSUnknown  _ = fail "Unknown field"
78
checkRS RSNoData   _ = fail "No data for a field"
79
checkRS RSUnavail  _ = fail "Ganeti reports unavailable data"
80
checkRS RSOffline  _ = fail "Ganeti reports resource as offline"
81

    
82
-- | Type of a query field.
83
$(declareSADT "FieldType"
84
  [ ("QFTUnknown",   'C.qftUnknown )
85
  , ("QFTText",      'C.qftText )
86
  , ("QFTBool",      'C.qftBool )
87
  , ("QFTNumber",    'C.qftNumber )
88
  , ("QFTUnit",      'C.qftUnit )
89
  , ("QFTTimestamp", 'C.qftTimestamp )
90
  , ("QFTOther",     'C.qftOther )
91
  ])
92
$(makeJSONInstance ''FieldType)
93

    
94
-- | Supported items on which Qlang works.
95
$(declareSADT "ItemType"
96
  [ ("QRCluster",  'C.qrCluster )
97
  , ("QRInstance", 'C.qrInstance )
98
  , ("QRNode",     'C.qrNode )
99
  , ("QRLock",     'C.qrLock )
100
  , ("QRGroup",    'C.qrGroup )
101
  , ("QROs",       'C.qrOs )
102
  , ("QRJob",      'C.qrJob )
103
  , ("QRExport",   'C.qrExport )
104
  ])
105
$(makeJSONInstance ''ItemType)
106

    
107
-- * Sub data types for query2 queries and responses.
108

    
109
-- | List of requested fields.
110
type Fields = [ String ]
111

    
112
-- | Query2 filter expression. It's a parameteric type since we can
113
-- filter different \"things\"; e.g. field names, or actual field
114
-- getters, etc.
115
data Filter a
116
    = EmptyFilter                   -- ^ No filter at all
117
    | AndFilter      [ Filter a ]   -- ^ & [<expression>, ...]
118
    | OrFilter       [ Filter a ]   -- ^ | [<expression>, ...]
119
    | NotFilter      (Filter a)     -- ^ ! <expression>
120
    | TrueFilter     a              -- ^ ? <field>
121
    | EQFilter       a FilterValue  -- ^ (=|!=) <field> <value>
122
    | LTFilter       a FilterValue  -- ^ < <field> <value>
123
    | GTFilter       a FilterValue  -- ^ > <field> <value>
124
    | LEFilter       a FilterValue  -- ^ <= <field> <value>
125
    | GEFilter       a FilterValue  -- ^ >= <field> <value>
126
    | RegexpFilter   a FilterRegexp -- ^ =~ <field> <regexp>
127
    | ContainsFilter a FilterValue  -- ^ =[] <list-field> <value>
128
      deriving (Show, Read, Eq)
129

    
130
-- | Serialiser for the 'Filter' data type.
131
showFilter :: (JSON a) => Filter a -> JSValue
132
showFilter (EmptyFilter)          = JSNull
133
showFilter (AndFilter exprs)      =
134
  JSArray $ (showJSON C.qlangOpAnd):(map showJSON exprs)
135
showFilter (OrFilter  exprs)      =
136
  JSArray $ (showJSON C.qlangOpOr):(map showJSON exprs)
137
showFilter (NotFilter flt)        =
138
  JSArray [showJSON C.qlangOpNot, showJSON flt]
139
showFilter (TrueFilter field)     =
140
  JSArray [showJSON C.qlangOpTrue, showJSON field]
141
showFilter (EQFilter field value) =
142
  JSArray [showJSON C.qlangOpEqual, showJSON field, showJSON value]
143
showFilter (LTFilter field value) =
144
  JSArray [showJSON C.qlangOpLt, showJSON field, showJSON value]
145
showFilter (GTFilter field value) =
146
  JSArray [showJSON C.qlangOpGt, showJSON field, showJSON value]
147
showFilter (LEFilter field value) =
148
  JSArray [showJSON C.qlangOpLe, showJSON field, showJSON value]
149
showFilter (GEFilter field value) =
150
  JSArray [showJSON C.qlangOpGe, showJSON field, showJSON value]
151
showFilter (RegexpFilter field regexp) =
152
  JSArray [showJSON C.qlangOpRegexp, showJSON field, showJSON regexp]
153
showFilter (ContainsFilter field value) =
154
  JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
155

    
156
-- | Deserializer for the 'Filter' data type.
157
readFilter :: (JSON a) => JSValue -> Result (Filter a)
158
readFilter JSNull = Ok EmptyFilter
159
readFilter (JSArray (JSString op:args)) =
160
  readFilterArray (fromJSString op) args
161
readFilter v =
162
  Error $ "Cannot deserialise filter: expected array [string, args], got " ++
163
        show (pp_value v)
164

    
165
-- | Helper to deserialise an array corresponding to a single filter
166
-- and return the built filter. Note this looks generic but is (at
167
-- least currently) only used for the NotFilter.
168
readFilterArg :: (JSON a) =>
169
                 (Filter a -> Filter a) -- ^ Constructor
170
              -> [JSValue]              -- ^ Single argument
171
              -> Result (Filter a)
172
readFilterArg constr [flt] = constr <$> readJSON flt
173
readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]\
174
                            \ but got " ++ show (pp_value (showJSON v))
175

    
176
-- | Helper to deserialise an array corresponding to a single field
177
-- and return the built filter.
178
readFilterField :: (JSON a) =>
179
                   (a -> Filter a)   -- ^ Constructor
180
                -> [JSValue]         -- ^ Single argument
181
                -> Result (Filter a)
182
readFilterField constr [field] = constr <$> readJSON field
183
readFilterField _ v = Error $ "Cannot deserialise field, expected [fieldname]\
184
                              \ but got " ++ show (pp_value (showJSON v))
185

    
186
-- | Helper to deserialise an array corresponding to a field and
187
-- value, returning the built filter.
188
readFilterFieldValue :: (JSON a, JSON b) =>
189
                        (a -> b -> Filter a) -- ^ Constructor
190
                     -> [JSValue]            -- ^ Arguments array
191
                     -> Result (Filter a)
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))
197

    
198
-- | Inner deserialiser for 'Filter'.
199
readFilterArray :: (JSON a) => String -> [JSValue] -> Result (Filter a)
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 ++ "'"
213

    
214
instance (JSON a) => JSON (Filter a) where
215
  showJSON = showFilter
216
  readJSON = readFilter
217

    
218
-- Traversable implementation for 'Filter'.
219
traverseFlt :: (Applicative f) => (a -> f b) -> Filter a -> f (Filter b)
220
traverseFlt _ EmptyFilter       = pure EmptyFilter
221
traverseFlt f (AndFilter flts)  = AndFilter <$> (traverse (traverseFlt f) flts)
222
traverseFlt f (OrFilter  flts)  = OrFilter  <$> (traverse (traverseFlt f) flts)
223
traverseFlt f (NotFilter flt)   = NotFilter <$> (traverseFlt f flt)
224
traverseFlt f (TrueFilter a)    = TrueFilter <$> f a
225
traverseFlt f (EQFilter a fval) = EQFilter <$> f a <*> pure fval
226
traverseFlt f (LTFilter a fval) = LTFilter <$> f a <*> pure fval
227
traverseFlt f (GTFilter a fval) = GTFilter <$> f a <*> pure fval
228
traverseFlt f (LEFilter a fval) = LEFilter <$> f a <*> pure fval
229
traverseFlt f (GEFilter a fval) = GEFilter <$> f a <*> pure fval
230
traverseFlt f (RegexpFilter a re)     = RegexpFilter <$> f a <*> pure re
231
traverseFlt f (ContainsFilter a fval) = ContainsFilter <$> f a <*> pure fval
232

    
233
instance Traversable Filter where
234
  traverse = traverseFlt
235

    
236
instance Functor Filter where
237
  fmap = fmapDefault
238

    
239
instance Foldable Filter where
240
  foldMap = foldMapDefault
241

    
242
-- | Field name to filter on.
243
type FilterField = String
244

    
245
-- | Value to compare the field value to, for filtering purposes.
246
data FilterValue = QuotedString String
247
                 | NumericValue Integer
248
                   deriving (Read, Show, Eq)
249

    
250
-- | Serialiser for 'FilterValue'. The Python code just sends this to
251
-- JSON as-is, so we'll do the same.
252
showFilterValue :: FilterValue -> JSValue
253
showFilterValue (QuotedString str) = showJSON str
254
showFilterValue (NumericValue val) = showJSON val
255

    
256
-- | Decoder for 'FilterValue'. We have to see what it contains, since
257
-- the context doesn't give us hints on what to expect.
258
readFilterValue :: JSValue -> Result FilterValue
259
readFilterValue (JSString a) = Ok . QuotedString $ fromJSString a
260
readFilterValue (JSRational _ x) =
261
  if denominator x /= 1
262
    then Error $ "Cannot deserialise numeric filter value,\
263
                 \ expecting integral but\
264
                 \ got a fractional value: " ++ show x
265
    else Ok . NumericValue $ numerator x
266
readFilterValue v = Error $ "Cannot deserialise filter value, expecting\
267
                            \ string or integer, got " ++ show (pp_value v)
268

    
269
instance JSON FilterValue where
270
  showJSON = showFilterValue
271
  readJSON = readFilterValue
272

    
273
-- | Regexp to apply to the filter value, for filteriong purposes.
274
type FilterRegexp = String
275

    
276
-- | Name of a field.
277
type FieldName = String
278
-- | Title of a field, when represented in tabular format.
279
type FieldTitle = String
280
-- | Human redable description of a field.
281
type FieldDoc = String
282

    
283
-- | Definition of a field.
284
$(buildObject "FieldDefinition" "fdef"
285
  [ simpleField "name"  [t| FieldName  |] -- FIXME: the name has restrictions
286
  , simpleField "title" [t| FieldTitle |]
287
  , simpleField "kind"  [t| FieldType  |]
288
  , simpleField "doc"   [t| FieldDoc   |]
289
  ])
290

    
291
--- | Single field entry result.
292
data ResultEntry = ResultEntry ResultStatus (Maybe ResultValue)
293
                   deriving (Show, Read, Eq)
294

    
295
instance JSON ResultEntry where
296
  showJSON (ResultEntry rs rv) =
297
    showJSON (showJSON rs, maybe JSNull showJSON rv)
298
  readJSON v = do
299
    (rs, rv) <- readJSON v
300
    rv' <- case rv of
301
             JSNull -> return Nothing
302
             x -> readJSON x
303
    return $ ResultEntry rs rv'
304

    
305
-- | The type of one result row.
306
type ResultRow = [ ResultEntry ]
307

    
308
-- | Value of a field, in json encoding.
309
-- (its type will be depending on ResultStatus and FieldType)
310
type ResultValue = JSValue
311

    
312
-- * Main Qlang queries and responses.
313

    
314
-- | Query2 query.
315
data Query = Query ItemType Fields (Filter FilterField)
316

    
317
-- | Query2 result.
318
$(buildObject "QueryResult" "qres"
319
  [ simpleField "fields" [t| [ FieldDefinition ] |]
320
  , simpleField "data"   [t| [ ResultRow       ] |]
321
  ])
322

    
323
-- | Query2 Fields query.
324
-- (to get supported fields names, descriptions, and types)
325
data QueryFields = QueryFields ItemType Fields
326

    
327
-- | Query2 Fields result.
328
$(buildObject "QueryFieldsResult" "qfieldres"
329
  [ simpleField "fields" [t| [FieldDefinition ] |]
330
  ])