Revision 91c1a265

b/src/Ganeti/Query/Export.hs
58 58
exportFields :: FieldList Node Runtime
59 59
exportFields =
60 60
  [ (FieldDefinition "node" "Node" QFTText "Node name",
61
     FieldSimple (rsNormal . nodeName), QffNormal)
61
     FieldSimple (rsNormal . nodeName), QffHostname)
62 62
  , (FieldDefinition "export" "Export" QFTText "Export name",
63 63
     FieldRuntime (curry fst), QffNormal)
64 64
  ]
b/src/Ganeti/Query/Filter.hs
25 25

  
26 26
{-
27 27

  
28
Copyright (C) 2012 Google Inc.
28
Copyright (C) 2012, 2013 Google Inc.
29 29

  
30 30
This program is free software; you can redistribute it and/or modify
31 31
it under the terms of the GNU General Public License as published by
......
79 79
-- | Processes a field value given a QffMode.
80 80
qffField :: QffMode -> JSValue -> ErrorResult JSValue
81 81
qffField QffNormal    v = Ok v
82
qffField QffHostname  v = Ok v
82 83
qffField QffTimestamp v =
83 84
  case v of
84 85
    JSArray [secs@(JSRational _ _), JSRational _ _] -> return secs
......
92 93
           -> Maybe b
93 94
           -> a
94 95
           -> (FieldGetter a b, QffMode)
95
           -> (JSValue -> ErrorResult Bool)
96
           -> (QffMode -> JSValue -> ErrorResult Bool)
96 97
           -> ErrorResult Bool
97 98
wrapGetter cfg b a (getter, qff) faction =
98 99
  case tryGetter cfg b a getter of
99 100
    Nothing -> Ok True -- runtime missing, accepting the value
100 101
    Just v ->
101 102
      case v of
102
        ResultEntry RSNormal (Just fval) -> qffField qff fval >>= faction
103
        ResultEntry RSNormal (Just fval) -> qffField qff fval >>= faction qff
103 104
        ResultEntry RSNormal Nothing ->
104 105
          Bad $ ProgrammerError
105 106
                "Internal error: Getter returned RSNormal/Nothing"
106 107
        _ -> Ok True -- filter has no data to work, accepting it
107 108

  
109
-- | Wrapper alias over field functions to ignore their first Qff argument.
110
ignoreMode :: a -> QffMode -> a
111
ignoreMode = const
112

  
108 113
-- | Helper to evaluate a filter getter (and the value it generates) in
109 114
-- a boolean context.
110 115
trueFilter :: JSValue -> ErrorResult Bool
......
118 123
-- and for them to be used in multiple contexts.
119 124
type Comparator = (Eq a, Ord a) => a -> a -> Bool
120 125

  
126
-- | Equality checker.
127
--
128
-- This will handle hostnames correctly, if the mode is set to
129
-- 'QffHostname'.
130
eqFilter :: FilterValue -> QffMode -> JSValue -> ErrorResult Bool
131
-- send 'QffNormal' queries to 'binOpFilter'
132
eqFilter flv QffNormal    jsv = binOpFilter (==) flv jsv
133
-- and 'QffTimestamp' as well
134
eqFilter flv QffTimestamp jsv = binOpFilter (==) flv jsv
135
-- error out if we set 'QffHostname' on a non-string field
136
eqFilter _ QffHostname (JSRational _ _) =
137
  Bad . ProgrammerError $ "QffHostname field returned a numeric value"
138
-- test strings via 'compareNameComponent'
139
eqFilter (QuotedString y) QffHostname (JSString x) =
140
  Ok $ goodLookupResult (fromJSString x `compareNameComponent` y)
141
-- send all other combinations (all errors) to 'binOpFilter', which
142
-- has good error messages
143
eqFilter flv _ jsv = binOpFilter (==) flv jsv
144

  
121 145
-- | Helper to evaluate a filder getter (and the value it generates)
122 146
-- in a boolean context. Note the order of arguments is reversed from
123 147
-- the filter definitions (due to the call chain), make sure to
......
178 202
evaluateFilter c mb a (NotFilter flt)  =
179 203
  not <$> evaluateFilter c mb a flt
180 204
evaluateFilter c mb a (TrueFilter getter)  =
181
  wrapGetter c mb a getter trueFilter
205
  wrapGetter c mb a getter $ ignoreMode trueFilter
182 206
evaluateFilter c mb a (EQFilter getter val) =
183
  wrapGetter c mb a getter (binOpFilter (==) val)
207
  wrapGetter c mb a getter (eqFilter val)
184 208
evaluateFilter c mb a (LTFilter getter val) =
185
  wrapGetter c mb a getter (binOpFilter (<) val)
209
  wrapGetter c mb a getter $ ignoreMode (binOpFilter (<) val)
186 210
evaluateFilter c mb a (LEFilter getter val) =
187
  wrapGetter c mb a getter (binOpFilter (<=) val)
211
  wrapGetter c mb a getter $ ignoreMode (binOpFilter (<=) val)
188 212
evaluateFilter c mb a (GTFilter getter val) =
189
  wrapGetter c mb a getter (binOpFilter (>) val)
213
  wrapGetter c mb a getter $ ignoreMode (binOpFilter (>) val)
190 214
evaluateFilter c mb a (GEFilter getter val) =
191
  wrapGetter c mb a getter (binOpFilter (>=) val)
215
  wrapGetter c mb a getter $ ignoreMode (binOpFilter (>=) val)
192 216
evaluateFilter c mb a (RegexpFilter getter re) =
193
  wrapGetter c mb a getter (regexpFilter re)
217
  wrapGetter c mb a getter $ ignoreMode (regexpFilter re)
194 218
evaluateFilter c mb a (ContainsFilter getter val) =
195
  wrapGetter c mb a getter (containsFilter val)
219
  wrapGetter c mb a getter $ ignoreMode (containsFilter val)
196 220

  
197 221
-- | Runs a getter with potentially missing runtime context.
198 222
tryGetter :: ConfigData -> Maybe b -> a -> FieldGetter a b -> Maybe ResultEntry
b/src/Ganeti/Query/Node.hs
140 140
       "Whether node can become a master candidate",
141 141
     FieldSimple (rsNormal . nodeMasterCapable), QffNormal)
142 142
  , (FieldDefinition "name" "Node" QFTText "Node name",
143
     FieldSimple (rsNormal . nodeName), QffNormal)
143
     FieldSimple (rsNormal . nodeName), QffHostname)
144 144
  , (FieldDefinition "offline" "Offline" QFTBool
145 145
       "Whether node is marked offline",
146 146
     FieldSimple (rsNormal . nodeOffline), QffNormal)
b/src/Ganeti/Query/Types.hs
7 7

  
8 8
{-
9 9

  
10
Copyright (C) 2012 Google Inc.
10
Copyright (C) 2012, 2013 Google Inc.
11 11

  
12 12
This program is free software; you can redistribute it and/or modify
13 13
it under the terms of the GNU General Public License as published by
......
56 56
-- don't use OR-able values.
57 57
data QffMode = QffNormal     -- ^ Value is used as-is in filters
58 58
             | QffTimestamp  -- ^ Value is a timestamp tuple, convert to float
59
             | QffHostname   -- ^ Value is a hostname, compare it smartly
59 60
               deriving (Show, Eq)
60 61

  
61 62

  
b/test/hs/Test/Ganeti/Query/Query.hs
1
{-# LANGUAGE TemplateHaskell #-}
1
{-# LANGUAGE TemplateHaskell, BangPatterns #-}
2 2
{-# OPTIONS_GHC -fno-warn-orphans #-}
3 3

  
4 4
{-| Unittests for ganeti-htools.
......
36 36
import Data.List
37 37
import qualified Data.Map as Map
38 38
import Data.Maybe
39
import qualified Data.Set as Set
39 40
import Text.JSON (JSValue(..), showJSON)
40 41

  
41 42
import Test.Ganeti.TestHelper
......
44 45

  
45 46
import Ganeti.BasicTypes
46 47
import Ganeti.Errors
48
import Ganeti.JSON
49
import Ganeti.Objects
47 50
import Ganeti.Query.Filter
48 51
import qualified Ganeti.Query.Group as Group
49 52
import Ganeti.Query.Language
50 53
import qualified Ganeti.Query.Node as Node
51 54
import Ganeti.Query.Query
52 55
import qualified Ganeti.Query.Job as Job
56
import Ganeti.Utils (sepSplit)
53 57

  
54 58
{-# ANN module "HLint: ignore Use camelCase" #-}
55 59

  
......
163 167
    (sortBy field_sort . map (\(f, _, _) -> f) $ Map.elems Node.fieldsMap)
164 168
    (sortBy field_sort fdefs)
165 169

  
170
-- | Check if cluster node names are unique (first elems).
171
areNodeNamesSane :: ConfigData -> Bool
172
areNodeNamesSane cfg =
173
  let fqdns = map nodeName . Map.elems . fromContainer $ configNodes cfg
174
      names = map (head . sepSplit '.') fqdns
175
  in length names == length (nub names)
176

  
177
-- | Check that the nodes reported by a name filter are sane.
178
prop_queryNode_filter :: Property
179
prop_queryNode_filter =
180
  forAll (choose (1, maxNodes)) $ \nodes ->
181
  forAll (genEmptyCluster nodes `suchThat`
182
          areNodeNamesSane) $ \cluster -> monadicIO $ do
183
    let node_list = map nodeName . Map.elems . fromContainer $
184
                    configNodes cluster
185
    count <- pick $ choose (1, nodes)
186
    fqdn_set <- pick . genSetHelper node_list $ Just count
187
    let fqdns = Set.elems fqdn_set
188
        names = map (head . sepSplit '.') fqdns
189
        flt = makeSimpleFilter "name" $ map Left names
190
    QueryResult _ fdata <-
191
      run (query cluster False (Query (ItemTypeOpCode QRNode)
192
                                ["name"] flt)) >>= resultProp
193
    stop $ conjoin
194
      [ printTestCase "Invalid node names" $
195
        map (map rentryValue) fdata ==? map (\f -> [Just (showJSON f)]) fqdns
196
      ]
197

  
166 198
-- ** Group queries
167 199

  
168 200
prop_queryGroup_noUnknown :: Property
......
328 360
  [ 'prop_queryNode_noUnknown
329 361
  , 'prop_queryNode_Unknown
330 362
  , 'prop_queryNode_types
363
  , 'prop_queryNode_filter
331 364
  , 'case_queryNode_allfields
332 365
  , 'prop_queryGroup_noUnknown
333 366
  , 'prop_queryGroup_Unknown

Also available in: Unified diff