Revision 4cab6703

b/Makefile.am
415 415
	htools/Ganeti/Luxi.hs \
416 416
	htools/Ganeti/Objects.hs \
417 417
	htools/Ganeti/OpCodes.hs \
418
	htools/Ganeti/Rpc.hs \
419
	htools/Ganeti/Qlang.hs \
420 418
	htools/Ganeti/Query/Common.hs \
421 419
	htools/Ganeti/Query/Filter.hs \
420
	htools/Ganeti/Query/Language.hs \
422 421
	htools/Ganeti/Query/Node.hs \
423 422
	htools/Ganeti/Query/Query.hs \
423
	htools/Ganeti/Query/Server.hs \
424 424
	htools/Ganeti/Query/Types.hs \
425
	htools/Ganeti/Queryd.hs \
425
	htools/Ganeti/Rpc.hs \
426 426
	htools/Ganeti/Runtime.hs \
427 427
	htools/Ganeti/Ssconf.hs \
428 428
	htools/Ganeti/THH.hs
b/htools/Ganeti/Confd/Server.hs
55 55
import Ganeti.Hash
56 56
import Ganeti.Logging
57 57
import qualified Ganeti.Constants as C
58
import Ganeti.Queryd (runQueryD)
58
import Ganeti.Query.Server (runQueryD)
59 59

  
60 60
-- * Types and constants definitions
61 61

  
b/htools/Ganeti/HTools/Luxi.hs
33 33
import qualified Text.JSON
34 34

  
35 35
import qualified Ganeti.Luxi as L
36
import qualified Ganeti.Qlang as Qlang
36
import qualified Ganeti.Query.Language as Qlang
37 37
import Ganeti.HTools.Loader
38 38
import Ganeti.HTools.Types
39 39
import qualified Ganeti.HTools.Group as Group
b/htools/Ganeti/HTools/QC.hs
88 88
import qualified Ganeti.Luxi as Luxi
89 89
import qualified Ganeti.Objects as Objects
90 90
import qualified Ganeti.OpCodes as OpCodes
91
import qualified Ganeti.Qlang as Qlang
92 91
import qualified Ganeti.Rpc as Rpc
92
import qualified Ganeti.Query.Language as Qlang
93 93
import qualified Ganeti.Runtime as Runtime
94 94
import qualified Ganeti.Ssconf as Ssconf
95 95
import qualified Ganeti.HTools.CLI as CLI
b/htools/Ganeti/Luxi.hs
74 74
import Ganeti.Constants
75 75
import Ganeti.Jobs (JobStatus)
76 76
import Ganeti.OpCodes (OpCode)
77
import qualified Ganeti.Qlang as Qlang
77
import qualified Ganeti.Query.Language as Qlang
78 78
import Ganeti.THH
79 79

  
80 80
-- * Utility functions
/dev/null
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
  ])
b/htools/Ganeti/Query/Common.hs
45 45
import qualified Ganeti.Constants as C
46 46
import Ganeti.Config
47 47
import Ganeti.Objects
48
import Ganeti.Qlang
48
import Ganeti.Query.Language
49 49
import Ganeti.Query.Types
50 50

  
51 51
-- * Generic functions
b/htools/Ganeti/Query/Filter.hs
58 58

  
59 59
import Ganeti.BasicTypes
60 60
import Ganeti.Objects
61
import Ganeti.Qlang
61
import Ganeti.Query.Language
62 62
import Ganeti.Query.Types
63 63
import Ganeti.HTools.JSON
64 64

  
b/htools/Ganeti/Query/Language.hs
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.Query.Language
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
  ])
b/htools/Ganeti/Query/Node.hs
34 34

  
35 35
import Ganeti.Config
36 36
import Ganeti.Objects
37
import Ganeti.Qlang
37
import Ganeti.Query.Language
38 38
import Ganeti.Query.Common
39 39
import Ganeti.Query.Types
40 40

  
b/htools/Ganeti/Query/Query.hs
56 56

  
57 57
import Ganeti.BasicTypes
58 58
import Ganeti.HTools.JSON
59
import Ganeti.Qlang
59
import Ganeti.Query.Language
60 60
import Ganeti.Query.Common
61 61
import Ganeti.Query.Filter
62 62
import Ganeti.Query.Types
b/htools/Ganeti/Query/Server.hs
1
{-# LANGUAGE BangPatterns #-}
2

  
3
{-| Implementation of the Ganeti confd types.
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.Query.Server
29
  ( ConfigReader
30
  , runQueryD
31
  ) where
32

  
33
import Control.Applicative
34
import Control.Concurrent
35
import Control.Exception
36
import Data.Bits (bitSize)
37
import Data.Maybe
38
import qualified Network.Socket as S
39
import qualified Text.JSON as J
40
import Text.JSON (showJSON, JSValue(..))
41
import Text.JSON.Pretty (pp_value)
42
import System.Info (arch)
43

  
44
import qualified Ganeti.Constants as C
45
import Ganeti.Daemon
46
import Ganeti.Objects
47
import qualified Ganeti.Config as Config
48
import Ganeti.BasicTypes
49
import Ganeti.Logging
50
import Ganeti.Luxi
51
import qualified Ganeti.Query.Language as Qlang
52
import Ganeti.Query.Query
53

  
54
-- | A type for functions that can return the configuration when
55
-- executed.
56
type ConfigReader = IO (Result ConfigData)
57

  
58
-- | Minimal wrapper to handle the missing config case.
59
handleCallWrapper :: Result ConfigData -> LuxiOp -> IO (Result JSValue)
60
handleCallWrapper (Bad msg) _ =
61
  return . Bad $ "I do not have access to a valid configuration, cannot\
62
                 \ process queries: " ++ msg
63
handleCallWrapper (Ok config) op = handleCall config op
64

  
65
-- | Actual luxi operation handler.
66
handleCall :: ConfigData -> LuxiOp -> IO (Result JSValue)
67
handleCall cdata QueryClusterInfo =
68
  let cluster = configCluster cdata
69
      hypervisors = clusterEnabledHypervisors cluster
70
      bits = show (bitSize (0::Int)) ++ "bits"
71
      arch_tuple = [bits, arch]
72
      obj = [ ("software_version", showJSON $ C.releaseVersion)
73
            , ("protocol_version", showJSON $ C.protocolVersion)
74
            , ("config_version", showJSON $ C.configVersion)
75
            , ("os_api_version", showJSON $ maximum C.osApiVersions)
76
            , ("export_version", showJSON $ C.exportVersion)
77
            , ("architecture", showJSON $ arch_tuple)
78
            , ("name", showJSON $ clusterClusterName cluster)
79
            , ("master", showJSON $ clusterMasterNode cluster)
80
            , ("default_hypervisor", showJSON $ head hypervisors)
81
            , ("enabled_hypervisors", showJSON $ hypervisors)
82
            , ("hvparams", showJSON $ clusterHvparams cluster)
83
            , ("os_hvp", showJSON $ clusterOsHvp cluster)
84
            , ("beparams", showJSON $ clusterBeparams cluster)
85
            , ("osparams", showJSON $ clusterOsparams cluster)
86
            , ("ipolicy", showJSON $ clusterIpolicy cluster)
87
            , ("nicparams", showJSON $ clusterNicparams cluster)
88
            , ("ndparams", showJSON $ clusterNdparams cluster)
89
            , ("diskparams", showJSON $ clusterDiskparams cluster)
90
            , ("candidate_pool_size",
91
               showJSON $ clusterCandidatePoolSize cluster)
92
            , ("master_netdev",  showJSON $ clusterMasterNetdev cluster)
93
            , ("master_netmask", showJSON $ clusterMasterNetmask cluster)
94
            , ("use_external_mip_script",
95
               showJSON $ clusterUseExternalMipScript cluster)
96
            , ("volume_group_name", showJSON $clusterVolumeGroupName cluster)
97
            , ("drbd_usermode_helper",
98
               maybe JSNull showJSON (clusterDrbdUsermodeHelper cluster))
99
            , ("file_storage_dir", showJSON $ clusterFileStorageDir cluster)
100
            , ("shared_file_storage_dir",
101
               showJSON $ clusterSharedFileStorageDir cluster)
102
            , ("maintain_node_health",
103
               showJSON $ clusterMaintainNodeHealth cluster)
104
            , ("ctime", showJSON $ clusterCtime cluster)
105
            , ("mtime", showJSON $ clusterMtime cluster)
106
            , ("uuid", showJSON $ clusterUuid cluster)
107
            , ("tags", showJSON $ clusterTags cluster)
108
            , ("uid_pool", showJSON $ clusterUidPool cluster)
109
            , ("default_iallocator",
110
               showJSON $ clusterDefaultIallocator cluster)
111
            , ("reserved_lvs", showJSON $ clusterReservedLvs cluster)
112
            , ("primary_ip_version",
113
               showJSON . ipFamilyToVersion $ clusterPrimaryIpFamily cluster)
114
             , ("prealloc_wipe_disks",
115
                showJSON $ clusterPreallocWipeDisks cluster)
116
             , ("hidden_os", showJSON $ clusterHiddenOs cluster)
117
             , ("blacklisted_os", showJSON $ clusterBlacklistedOs cluster)
118
            ]
119

  
120
  in return . Ok . J.makeObj $ obj
121

  
122
handleCall cfg (QueryTags kind name) =
123
  let tags = case kind of
124
               TagCluster -> Ok . clusterTags $ configCluster cfg
125
               TagGroup -> groupTags <$> Config.getGroup cfg name
126
               TagNode -> nodeTags <$> Config.getNode cfg name
127
               TagInstance -> instTags <$> Config.getInstance cfg name
128
  in return (J.showJSON <$> tags)
129

  
130
handleCall cfg (Query qkind qfields qfilter) = do
131
  result <- query cfg (Qlang.Query qkind qfields qfilter)
132
  return $ J.showJSON <$> result
133

  
134
handleCall _ (QueryFields qkind qfields) = do
135
  let result = queryFields (Qlang.QueryFields qkind qfields)
136
  return $ J.showJSON <$> result
137

  
138
handleCall _ op =
139
  return . Bad $ "Luxi call '" ++ strOfOp op ++ "' not implemented"
140

  
141

  
142
-- | Given a decoded luxi request, executes it and sends the luxi
143
-- response back to the client.
144
handleClientMsg :: Client -> ConfigReader -> LuxiOp -> IO Bool
145
handleClientMsg client creader args = do
146
  cfg <- creader
147
  logDebug $ "Request: " ++ show args
148
  call_result <- handleCallWrapper cfg args
149
  (!status, !rval) <-
150
    case call_result of
151
      Bad err -> do
152
        let errmsg = "Failed to execute request: " ++ err
153
        logWarning errmsg
154
        return (False, showJSON errmsg)
155
      Ok result -> do
156
        logDebug $ "Result " ++ show (pp_value result)
157
        return (True, result)
158
  sendMsg client $ buildResponse status rval
159
  return True
160

  
161
-- | Handles one iteration of the client protocol: receives message,
162
-- checks for validity and decods, returns response.
163
handleClient :: Client -> ConfigReader -> IO Bool
164
handleClient client creader = do
165
  !msg <- recvMsgExt client
166
  case msg of
167
    RecvConnClosed -> logDebug "Connection closed" >> return False
168
    RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
169
                     return False
170
    RecvOk payload ->
171
      case validateCall payload >>= decodeCall of
172
        Bad err -> do
173
             let errmsg = "Failed to parse request: " ++ err
174
             logWarning errmsg
175
             sendMsg client $ buildResponse False (showJSON errmsg)
176
             return False
177
        Ok args -> handleClientMsg client creader args
178

  
179
-- | Main client loop: runs one loop of 'handleClient', and if that
180
-- doesn't repot a finished (closed) connection, restarts itself.
181
clientLoop :: Client -> ConfigReader -> IO ()
182
clientLoop client creader = do
183
  result <- handleClient client creader
184
  if result
185
    then clientLoop client creader
186
    else closeClient client
187

  
188
-- | Main loop: accepts clients, forks an I/O thread to handle that
189
-- client, and then restarts.
190
mainLoop :: ConfigReader -> S.Socket -> IO ()
191
mainLoop creader socket = do
192
  client <- acceptClient socket
193
  _ <- forkIO $ clientLoop client creader
194
  mainLoop creader socket
195

  
196
-- | Main function that runs the query endpoint. This should be the
197
-- only one exposed from this module.
198
runQueryD :: Maybe FilePath -> ConfigReader -> IO ()
199
runQueryD fpath creader = do
200
  let socket_path = fromMaybe C.querySocket fpath
201
  cleanupSocket socket_path
202
  bracket
203
    (getServer socket_path)
204
    (closeServer socket_path)
205
    (mainLoop creader)
b/htools/Ganeti/Query/Types.hs
30 30

  
31 31
import qualified Data.Map as Map
32 32

  
33
import Ganeti.Qlang
33
import Ganeti.Query.Language
34 34
import Ganeti.Objects
35 35

  
36 36
-- | The type of field getters. The \"a\" type represents the type
/dev/null
1
{-# LANGUAGE BangPatterns #-}
2

  
3
{-| Implementation of the Ganeti confd types.
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.Queryd
29
  ( ConfigReader
30
  , runQueryD
31
  ) where
32

  
33
import Control.Applicative
34
import Control.Concurrent
35
import Control.Exception
36
import Data.Bits (bitSize)
37
import Data.Maybe
38
import qualified Network.Socket as S
39
import qualified Text.JSON as J
40
import Text.JSON (showJSON, JSValue(..))
41
import Text.JSON.Pretty (pp_value)
42
import System.Info (arch)
43

  
44
import qualified Ganeti.Constants as C
45
import Ganeti.Daemon
46
import Ganeti.Objects
47
import qualified Ganeti.Config as Config
48
import Ganeti.BasicTypes
49
import Ganeti.Logging
50
import Ganeti.Luxi
51
import qualified Ganeti.Qlang as Qlang
52
import Ganeti.Query.Query
53

  
54
-- | A type for functions that can return the configuration when
55
-- executed.
56
type ConfigReader = IO (Result ConfigData)
57

  
58
-- | Minimal wrapper to handle the missing config case.
59
handleCallWrapper :: Result ConfigData -> LuxiOp -> IO (Result JSValue)
60
handleCallWrapper (Bad msg) _ =
61
  return . Bad $ "I do not have access to a valid configuration, cannot\
62
                 \ process queries: " ++ msg
63
handleCallWrapper (Ok config) op = handleCall config op
64

  
65
-- | Actual luxi operation handler.
66
handleCall :: ConfigData -> LuxiOp -> IO (Result JSValue)
67
handleCall cdata QueryClusterInfo =
68
  let cluster = configCluster cdata
69
      hypervisors = clusterEnabledHypervisors cluster
70
      bits = show (bitSize (0::Int)) ++ "bits"
71
      arch_tuple = [bits, arch]
72
      obj = [ ("software_version", showJSON $ C.releaseVersion)
73
            , ("protocol_version", showJSON $ C.protocolVersion)
74
            , ("config_version", showJSON $ C.configVersion)
75
            , ("os_api_version", showJSON $ maximum C.osApiVersions)
76
            , ("export_version", showJSON $ C.exportVersion)
77
            , ("architecture", showJSON $ arch_tuple)
78
            , ("name", showJSON $ clusterClusterName cluster)
79
            , ("master", showJSON $ clusterMasterNode cluster)
80
            , ("default_hypervisor", showJSON $ head hypervisors)
81
            , ("enabled_hypervisors", showJSON $ hypervisors)
82
            , ("hvparams", showJSON $ clusterHvparams cluster)
83
            , ("os_hvp", showJSON $ clusterOsHvp cluster)
84
            , ("beparams", showJSON $ clusterBeparams cluster)
85
            , ("osparams", showJSON $ clusterOsparams cluster)
86
            , ("ipolicy", showJSON $ clusterIpolicy cluster)
87
            , ("nicparams", showJSON $ clusterNicparams cluster)
88
            , ("ndparams", showJSON $ clusterNdparams cluster)
89
            , ("diskparams", showJSON $ clusterDiskparams cluster)
90
            , ("candidate_pool_size",
91
               showJSON $ clusterCandidatePoolSize cluster)
92
            , ("master_netdev",  showJSON $ clusterMasterNetdev cluster)
93
            , ("master_netmask", showJSON $ clusterMasterNetmask cluster)
94
            , ("use_external_mip_script",
95
               showJSON $ clusterUseExternalMipScript cluster)
96
            , ("volume_group_name", showJSON $clusterVolumeGroupName cluster)
97
            , ("drbd_usermode_helper",
98
               maybe JSNull showJSON (clusterDrbdUsermodeHelper cluster))
99
            , ("file_storage_dir", showJSON $ clusterFileStorageDir cluster)
100
            , ("shared_file_storage_dir",
101
               showJSON $ clusterSharedFileStorageDir cluster)
102
            , ("maintain_node_health",
103
               showJSON $ clusterMaintainNodeHealth cluster)
104
            , ("ctime", showJSON $ clusterCtime cluster)
105
            , ("mtime", showJSON $ clusterMtime cluster)
106
            , ("uuid", showJSON $ clusterUuid cluster)
107
            , ("tags", showJSON $ clusterTags cluster)
108
            , ("uid_pool", showJSON $ clusterUidPool cluster)
109
            , ("default_iallocator",
110
               showJSON $ clusterDefaultIallocator cluster)
111
            , ("reserved_lvs", showJSON $ clusterReservedLvs cluster)
112
            , ("primary_ip_version",
113
               showJSON . ipFamilyToVersion $ clusterPrimaryIpFamily cluster)
114
             , ("prealloc_wipe_disks",
115
                showJSON $ clusterPreallocWipeDisks cluster)
116
             , ("hidden_os", showJSON $ clusterHiddenOs cluster)
117
             , ("blacklisted_os", showJSON $ clusterBlacklistedOs cluster)
118
            ]
119

  
120
  in return . Ok . J.makeObj $ obj
121

  
122
handleCall cfg (QueryTags kind name) =
123
  let tags = case kind of
124
               TagCluster -> Ok . clusterTags $ configCluster cfg
125
               TagGroup -> groupTags <$> Config.getGroup cfg name
126
               TagNode -> nodeTags <$> Config.getNode cfg name
127
               TagInstance -> instTags <$> Config.getInstance cfg name
128
  in return (J.showJSON <$> tags)
129

  
130
handleCall cfg (Query qkind qfields qfilter) = do
131
  result <- query cfg (Qlang.Query qkind qfields qfilter)
132
  return $ J.showJSON <$> result
133

  
134
handleCall _ (QueryFields qkind qfields) = do
135
  let result = queryFields (Qlang.QueryFields qkind qfields)
136
  return $ J.showJSON <$> result
137

  
138
handleCall _ op =
139
  return . Bad $ "Luxi call '" ++ strOfOp op ++ "' not implemented"
140

  
141

  
142
-- | Given a decoded luxi request, executes it and sends the luxi
143
-- response back to the client.
144
handleClientMsg :: Client -> ConfigReader -> LuxiOp -> IO Bool
145
handleClientMsg client creader args = do
146
  cfg <- creader
147
  logDebug $ "Request: " ++ show args
148
  call_result <- handleCallWrapper cfg args
149
  (!status, !rval) <-
150
    case call_result of
151
      Bad err -> do
152
        let errmsg = "Failed to execute request: " ++ err
153
        logWarning errmsg
154
        return (False, showJSON errmsg)
155
      Ok result -> do
156
        logDebug $ "Result " ++ show (pp_value result)
157
        return (True, result)
158
  sendMsg client $ buildResponse status rval
159
  return True
160

  
161
-- | Handles one iteration of the client protocol: receives message,
162
-- checks for validity and decods, returns response.
163
handleClient :: Client -> ConfigReader -> IO Bool
164
handleClient client creader = do
165
  !msg <- recvMsgExt client
166
  case msg of
167
    RecvConnClosed -> logDebug "Connection closed" >> return False
168
    RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
169
                     return False
170
    RecvOk payload ->
171
      case validateCall payload >>= decodeCall of
172
        Bad err -> do
173
             let errmsg = "Failed to parse request: " ++ err
174
             logWarning errmsg
175
             sendMsg client $ buildResponse False (showJSON errmsg)
176
             return False
177
        Ok args -> handleClientMsg client creader args
178

  
179
-- | Main client loop: runs one loop of 'handleClient', and if that
180
-- doesn't repot a finished (closed) connection, restarts itself.
181
clientLoop :: Client -> ConfigReader -> IO ()
182
clientLoop client creader = do
183
  result <- handleClient client creader
184
  if result
185
    then clientLoop client creader
186
    else closeClient client
187

  
188
-- | Main loop: accepts clients, forks an I/O thread to handle that
189
-- client, and then restarts.
190
mainLoop :: ConfigReader -> S.Socket -> IO ()
191
mainLoop creader socket = do
192
  client <- acceptClient socket
193
  _ <- forkIO $ clientLoop client creader
194
  mainLoop creader socket
195

  
196
-- | Main function that runs the query endpoint. This should be the
197
-- only one exposed from this module.
198
runQueryD :: Maybe FilePath -> ConfigReader -> IO ()
199
runQueryD fpath creader = do
200
  let socket_path = fromMaybe C.querySocket fpath
201
  cleanupSocket socket_path
202
  bracket
203
    (getServer socket_path)
204
    (closeServer socket_path)
205
    (mainLoop creader)

Also available in: Unified diff