Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / Query / Query.hs @ a2ae14e9

History | View | Annotate | Download (8.3 kB)

1 4cbe9bda Iustin Pop
{-| Implementation of the Ganeti Query2 functionality.
2 4cbe9bda Iustin Pop
3 4cbe9bda Iustin Pop
 -}
4 4cbe9bda Iustin Pop
5 4cbe9bda Iustin Pop
{-
6 4cbe9bda Iustin Pop
7 4cbe9bda Iustin Pop
Copyright (C) 2012 Google Inc.
8 4cbe9bda Iustin Pop
9 4cbe9bda Iustin Pop
This program is free software; you can redistribute it and/or modify
10 4cbe9bda Iustin Pop
it under the terms of the GNU General Public License as published by
11 4cbe9bda Iustin Pop
the Free Software Foundation; either version 2 of the License, or
12 4cbe9bda Iustin Pop
(at your option) any later version.
13 4cbe9bda Iustin Pop
14 4cbe9bda Iustin Pop
This program is distributed in the hope that it will be useful, but
15 4cbe9bda Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
16 4cbe9bda Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 4cbe9bda Iustin Pop
General Public License for more details.
18 4cbe9bda Iustin Pop
19 4cbe9bda Iustin Pop
You should have received a copy of the GNU General Public License
20 4cbe9bda Iustin Pop
along with this program; if not, write to the Free Software
21 4cbe9bda Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 4cbe9bda Iustin Pop
02110-1301, USA.
23 4cbe9bda Iustin Pop
24 4cbe9bda Iustin Pop
-}
25 4cbe9bda Iustin Pop
26 8a65c02b Iustin Pop
{-
27 8a65c02b Iustin Pop
28 8a65c02b Iustin Pop
TODO: problems with the current model:
29 8a65c02b Iustin Pop
30 8a65c02b Iustin Pop
1. There's nothing preventing a result such as ResultEntry RSNormal
31 8a65c02b Iustin Pop
Nothing, or ResultEntry RSNoData (Just ...); ideally, we would
32 8a65c02b Iustin Pop
separate the the RSNormal and other types; we would need a new data
33 8a65c02b Iustin Pop
type for this, though, with JSON encoding/decoding
34 8a65c02b Iustin Pop
35 8a65c02b Iustin Pop
2. We don't have a way to 'bind' a FieldDefinition's field type
36 8a65c02b Iustin Pop
(e.q. QFTBool) with the actual value that is returned from a
37 8a65c02b Iustin Pop
FieldGetter. This means that the various getter functions can return
38 8a65c02b Iustin Pop
divergent types for the same field when evaluated against multiple
39 8a65c02b Iustin Pop
items. This is bad; it only works today because we 'hide' everything
40 8a65c02b Iustin Pop
behind JSValue, but is not nice at all. We should probably remove the
41 8a65c02b Iustin Pop
separation between FieldDefinition and the FieldGetter, and introduce
42 8a65c02b Iustin Pop
a new abstract data type, similar to QFT*, that contains the values
43 8a65c02b Iustin Pop
too.
44 8a65c02b Iustin Pop
45 8a65c02b Iustin Pop
-}
46 8a65c02b Iustin Pop
47 4cbe9bda Iustin Pop
module Ganeti.Query.Query
48 4cbe9bda Iustin Pop
    ( query
49 518023a9 Iustin Pop
    , queryFields
50 cd67e337 Iustin Pop
    , queryCompat
51 bc4cdeef Iustin Pop
    , getRequestedNames
52 cd67e337 Iustin Pop
    , nameField
53 4cbe9bda Iustin Pop
    ) where
54 4cbe9bda Iustin Pop
55 8a65c02b Iustin Pop
import Control.Monad (filterM)
56 7f0fd838 Agata Murawska
import Control.Monad.Trans (lift)
57 cd67e337 Iustin Pop
import Data.List (intercalate)
58 046fe3f5 Iustin Pop
import Data.Maybe (fromMaybe)
59 046fe3f5 Iustin Pop
import qualified Data.Map as Map
60 cd67e337 Iustin Pop
import qualified Text.JSON as J
61 046fe3f5 Iustin Pop
62 4cbe9bda Iustin Pop
import Ganeti.BasicTypes
63 0ec87781 Iustin Pop
import Ganeti.Config
64 f3baf5ef Iustin Pop
import Ganeti.JSON
65 7f0fd838 Agata Murawska
import Ganeti.Rpc
66 4cab6703 Iustin Pop
import Ganeti.Query.Language
67 046fe3f5 Iustin Pop
import Ganeti.Query.Common
68 8a65c02b Iustin Pop
import Ganeti.Query.Filter
69 046fe3f5 Iustin Pop
import Ganeti.Query.Types
70 046fe3f5 Iustin Pop
import Ganeti.Query.Node
71 40246fa0 Agata Murawska
import Ganeti.Query.Group
72 4cbe9bda Iustin Pop
import Ganeti.Objects
73 a41c337e Iustin Pop
import Ganeti.Utils
74 4cbe9bda Iustin Pop
75 046fe3f5 Iustin Pop
-- * Helper functions
76 046fe3f5 Iustin Pop
77 046fe3f5 Iustin Pop
-- | Builds an unknown field definition.
78 046fe3f5 Iustin Pop
mkUnknownFDef :: String -> FieldData a b
79 046fe3f5 Iustin Pop
mkUnknownFDef name =
80 046fe3f5 Iustin Pop
  ( FieldDefinition name name QFTUnknown ("Unknown field '" ++ name ++ "'")
81 046fe3f5 Iustin Pop
  , FieldUnknown )
82 046fe3f5 Iustin Pop
83 046fe3f5 Iustin Pop
-- | Runs a field getter on the existing contexts.
84 046fe3f5 Iustin Pop
execGetter :: ConfigData -> b -> a -> FieldGetter a b -> ResultEntry
85 046fe3f5 Iustin Pop
execGetter _   _ item (FieldSimple getter)  = getter item
86 046fe3f5 Iustin Pop
execGetter cfg _ item (FieldConfig getter)  = getter cfg item
87 046fe3f5 Iustin Pop
execGetter _  rt item (FieldRuntime getter) = getter rt item
88 046fe3f5 Iustin Pop
execGetter _   _ _    FieldUnknown          = rsUnknown
89 046fe3f5 Iustin Pop
90 046fe3f5 Iustin Pop
-- * Main query execution
91 046fe3f5 Iustin Pop
92 046fe3f5 Iustin Pop
-- | Helper to build the list of requested fields. This transforms the
93 046fe3f5 Iustin Pop
-- list of string fields to a list of field defs and getters, with
94 046fe3f5 Iustin Pop
-- some of them possibly being unknown fields.
95 046fe3f5 Iustin Pop
getSelectedFields :: FieldMap a b  -- ^ Defined fields
96 046fe3f5 Iustin Pop
                  -> [String]      -- ^ Requested fields
97 046fe3f5 Iustin Pop
                  -> FieldList a b -- ^ Selected fields
98 046fe3f5 Iustin Pop
getSelectedFields defined =
99 046fe3f5 Iustin Pop
  map (\name -> fromMaybe (mkUnknownFDef name) $ name `Map.lookup` defined)
100 046fe3f5 Iustin Pop
101 7f0fd838 Agata Murawska
-- | Collect live data from RPC query if enabled.
102 7f0fd838 Agata Murawska
-- FIXME: Check which fields we actually need and possibly send empty
103 7f0fd838 Agata Murawska
-- hvs/vgs if no info from hypervisor/volume group respectively
104 7f0fd838 Agata Murawska
-- is required
105 7f0fd838 Agata Murawska
maybeCollectLiveData:: Bool -> ConfigData -> [Node] -> IO [(Node, NodeRuntime)]
106 7f0fd838 Agata Murawska
107 7f0fd838 Agata Murawska
maybeCollectLiveData False _ nodes =
108 7f0fd838 Agata Murawska
  return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled"))
109 7f0fd838 Agata Murawska
110 7f0fd838 Agata Murawska
maybeCollectLiveData True cfg nodes = do
111 7f0fd838 Agata Murawska
  let vgs = [clusterVolumeGroupName $ configCluster cfg]
112 0ec87781 Iustin Pop
      hvs = [getDefaultHypervisor cfg]
113 7f0fd838 Agata Murawska
  executeRpcCall nodes (RpcCallNodeInfo vgs hvs)
114 7f0fd838 Agata Murawska
115 7f0fd838 Agata Murawska
-- | Check whether list of queried fields contains live fields.
116 7f0fd838 Agata Murawska
needsLiveData :: [FieldGetter a b] -> Bool
117 a2ae14e9 Iustin Pop
needsLiveData = any isRuntimeField
118 7f0fd838 Agata Murawska
119 bc4cdeef Iustin Pop
-- | Checks whether we have requested exactly some names. This is a
120 bc4cdeef Iustin Pop
-- simple wrapper over 'requestedNames' and 'nameField'.
121 bc4cdeef Iustin Pop
needsNames :: Query -> Maybe [FilterValue]
122 bc4cdeef Iustin Pop
needsNames (Query kind _ qfilter) = requestedNames (nameField kind) qfilter
123 bc4cdeef Iustin Pop
124 bc4cdeef Iustin Pop
-- | Computes the name field for different query types.
125 bc4cdeef Iustin Pop
nameField :: ItemType -> FilterField
126 bc4cdeef Iustin Pop
nameField QRJob = "id"
127 bc4cdeef Iustin Pop
nameField _     = "name"
128 bc4cdeef Iustin Pop
129 bc4cdeef Iustin Pop
-- | Extracts all quoted strings from a list, ignoring the
130 bc4cdeef Iustin Pop
-- 'NumericValue' entries.
131 bc4cdeef Iustin Pop
getAllQuotedStrings :: [FilterValue] -> [String]
132 bc4cdeef Iustin Pop
getAllQuotedStrings =
133 bc4cdeef Iustin Pop
  concatMap extractor
134 bc4cdeef Iustin Pop
    where extractor (NumericValue _)   = []
135 bc4cdeef Iustin Pop
          extractor (QuotedString val) = [val]
136 bc4cdeef Iustin Pop
137 bc4cdeef Iustin Pop
-- | Checks that we have either requested a valid set of names, or we
138 bc4cdeef Iustin Pop
-- have a more complex filter.
139 bc4cdeef Iustin Pop
getRequestedNames :: Query -> [String]
140 bc4cdeef Iustin Pop
getRequestedNames qry =
141 bc4cdeef Iustin Pop
  case needsNames qry of
142 bc4cdeef Iustin Pop
    Just names -> getAllQuotedStrings names
143 bc4cdeef Iustin Pop
    Nothing    -> []
144 bc4cdeef Iustin Pop
145 4cbe9bda Iustin Pop
-- | Main query execution function.
146 4cbe9bda Iustin Pop
query :: ConfigData   -- ^ The current configuration
147 fa2c927c Agata Murawska
      -> Bool         -- ^ Whether to collect live data
148 4cbe9bda Iustin Pop
      -> Query        -- ^ The query (item, fields, filter)
149 4cbe9bda Iustin Pop
      -> IO (Result QueryResult) -- ^ Result
150 a41c337e Iustin Pop
query cfg live qry = queryInner cfg live qry $ getRequestedNames qry
151 046fe3f5 Iustin Pop
152 a41c337e Iustin Pop
-- | Inner query execution function.
153 a41c337e Iustin Pop
queryInner :: ConfigData   -- ^ The current configuration
154 a41c337e Iustin Pop
           -> Bool         -- ^ Whether to collect live data
155 a41c337e Iustin Pop
           -> Query        -- ^ The query (item, fields, filter)
156 a41c337e Iustin Pop
           -> [String]     -- ^ Requested names
157 a41c337e Iustin Pop
           -> IO (Result QueryResult) -- ^ Result
158 a41c337e Iustin Pop
159 a41c337e Iustin Pop
queryInner cfg live (Query QRNode fields qfilter) wanted = runResultT $ do
160 7f0fd838 Agata Murawska
  cfilter <- resultT $ compileFilter nodeFieldsMap qfilter
161 046fe3f5 Iustin Pop
  let selected = getSelectedFields nodeFieldsMap fields
162 046fe3f5 Iustin Pop
      (fdefs, fgetters) = unzip selected
163 7f0fd838 Agata Murawska
      live' = live && needsLiveData fgetters
164 a41c337e Iustin Pop
  nodes <- resultT $ case wanted of
165 1fc3812c Iustin Pop
             [] -> Ok . niceSortKey nodeName .
166 1fc3812c Iustin Pop
                   Map.elems . fromContainer $ configNodes cfg
167 a41c337e Iustin Pop
             _  -> mapM (getNode cfg) wanted
168 8a65c02b Iustin Pop
  -- runs first pass of the filter, without a runtime context; this
169 8a65c02b Iustin Pop
  -- will limit the nodes that we'll contact for runtime data
170 7f0fd838 Agata Murawska
  fnodes <- resultT $ filterM (\n -> evaluateFilter cfg Nothing n cfilter) nodes
171 8a65c02b Iustin Pop
  -- here we would run the runtime data gathering, then filter again
172 8a65c02b Iustin Pop
  -- the nodes, based on existing runtime data
173 7f0fd838 Agata Murawska
  nruntimes <- lift $ maybeCollectLiveData live' cfg fnodes
174 7f0fd838 Agata Murawska
  let fdata = map (\(node, nrt) -> map (execGetter cfg nrt node) fgetters)
175 7f0fd838 Agata Murawska
              nruntimes
176 046fe3f5 Iustin Pop
  return QueryResult { qresFields = fdefs, qresData = fdata }
177 046fe3f5 Iustin Pop
178 a41c337e Iustin Pop
queryInner cfg _ (Query QRGroup fields qfilter) wanted = return $ do
179 40246fa0 Agata Murawska
  cfilter <- compileFilter groupFieldsMap qfilter
180 40246fa0 Agata Murawska
  let selected = getSelectedFields groupFieldsMap fields
181 40246fa0 Agata Murawska
      (fdefs, fgetters) = unzip selected
182 a41c337e Iustin Pop
  groups <- case wanted of
183 1fc3812c Iustin Pop
              [] -> Ok . niceSortKey groupName .
184 1fc3812c Iustin Pop
                    Map.elems . fromContainer $ configNodegroups cfg
185 a41c337e Iustin Pop
              _  -> mapM (getGroup cfg) wanted
186 40246fa0 Agata Murawska
  -- there is no live data for groups, so filtering is much simpler
187 40246fa0 Agata Murawska
  fgroups <- filterM (\n -> evaluateFilter cfg Nothing n cfilter) groups
188 40246fa0 Agata Murawska
  let fdata = map (\node ->
189 40246fa0 Agata Murawska
                       map (execGetter cfg GroupRuntime node) fgetters) fgroups
190 40246fa0 Agata Murawska
  return QueryResult {qresFields = fdefs, qresData = fdata }
191 40246fa0 Agata Murawska
192 a41c337e Iustin Pop
queryInner _ _ (Query qkind _ _) _ =
193 4cbe9bda Iustin Pop
  return . Bad $ "Query '" ++ show qkind ++ "' not supported"
194 518023a9 Iustin Pop
195 b04dc242 Iustin Pop
-- | Helper for 'queryFields'.
196 b04dc242 Iustin Pop
fieldsExtractor :: FieldMap a b -> [FilterField] -> QueryFieldsResult
197 b04dc242 Iustin Pop
fieldsExtractor fieldsMap fields =
198 b04dc242 Iustin Pop
  let selected = if null fields
199 b04dc242 Iustin Pop
                   then map snd $ Map.toAscList fieldsMap
200 b04dc242 Iustin Pop
                   else getSelectedFields fieldsMap fields
201 b04dc242 Iustin Pop
  in QueryFieldsResult (map fst selected)
202 b04dc242 Iustin Pop
203 518023a9 Iustin Pop
-- | Query fields call.
204 518023a9 Iustin Pop
queryFields :: QueryFields -> Result QueryFieldsResult
205 518023a9 Iustin Pop
queryFields (QueryFields QRNode fields) =
206 b04dc242 Iustin Pop
  Ok $ fieldsExtractor nodeFieldsMap fields
207 518023a9 Iustin Pop
208 40246fa0 Agata Murawska
queryFields (QueryFields QRGroup fields) =
209 b04dc242 Iustin Pop
  Ok $ fieldsExtractor groupFieldsMap fields
210 40246fa0 Agata Murawska
211 518023a9 Iustin Pop
queryFields (QueryFields qkind _) =
212 518023a9 Iustin Pop
  Bad $ "QueryFields '" ++ show qkind ++ "' not supported"
213 cd67e337 Iustin Pop
214 cd67e337 Iustin Pop
-- | Classic query converter. It gets a standard query result on input
215 cd67e337 Iustin Pop
-- and computes the classic style results.
216 cd67e337 Iustin Pop
queryCompat :: QueryResult -> Result [[J.JSValue]]
217 cd67e337 Iustin Pop
queryCompat (QueryResult fields qrdata) =
218 cd67e337 Iustin Pop
  case map fdefName $ filter ((== QFTUnknown) . fdefKind) fields of
219 cd67e337 Iustin Pop
    [] -> Ok $ map (map (maybe J.JSNull J.showJSON . rentryValue)) qrdata
220 cd67e337 Iustin Pop
    unknown -> Bad $ "Unknown output fields selected: " ++
221 cd67e337 Iustin Pop
                     intercalate ", " unknown