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