Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (7.9 kB)

1
{-| Implementation of the Ganeti Query2 functionality.
2

    
3
 -}
4

    
5
{-
6

    
7
Copyright (C) 2012 Google Inc.
8

    
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13

    
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
General Public License for more details.
18

    
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
02110-1301, USA.
23

    
24
-}
25

    
26
{-
27

    
28
TODO: problems with the current model:
29

    
30
1. There's nothing preventing a result such as ResultEntry RSNormal
31
Nothing, or ResultEntry RSNoData (Just ...); ideally, we would
32
separate the the RSNormal and other types; we would need a new data
33
type for this, though, with JSON encoding/decoding
34

    
35
2. We don't have a way to 'bind' a FieldDefinition's field type
36
(e.q. QFTBool) with the actual value that is returned from a
37
FieldGetter. This means that the various getter functions can return
38
divergent types for the same field when evaluated against multiple
39
items. This is bad; it only works today because we 'hide' everything
40
behind JSValue, but is not nice at all. We should probably remove the
41
separation between FieldDefinition and the FieldGetter, and introduce
42
a new abstract data type, similar to QFT*, that contains the values
43
too.
44

    
45
-}
46

    
47
module Ganeti.Query.Query
48

    
49
    ( query
50
    , queryFields
51
    , getRequestedNames
52
    ) where
53

    
54
import Control.Monad (filterM)
55
import Control.Monad.Trans (lift)
56
import Data.Maybe (fromMaybe)
57
import qualified Data.Map as Map
58

    
59
import Ganeti.BasicTypes
60
import Ganeti.Config
61
import Ganeti.JSON
62
import Ganeti.Rpc
63
import Ganeti.Query.Language
64
import Ganeti.Query.Common
65
import Ganeti.Query.Filter
66
import Ganeti.Query.Types
67
import Ganeti.Query.Node
68
import Ganeti.Query.Group
69
import Ganeti.Objects
70
import Ganeti.Utils
71

    
72
-- * Helper functions
73

    
74
-- | Builds an unknown field definition.
75
mkUnknownFDef :: String -> FieldData a b
76
mkUnknownFDef name =
77
  ( FieldDefinition name name QFTUnknown ("Unknown field '" ++ name ++ "'")
78
  , FieldUnknown )
79

    
80
-- | Runs a field getter on the existing contexts.
81
execGetter :: ConfigData -> b -> a -> FieldGetter a b -> ResultEntry
82
execGetter _   _ item (FieldSimple getter)  = getter item
83
execGetter cfg _ item (FieldConfig getter)  = getter cfg item
84
execGetter _  rt item (FieldRuntime getter) = getter rt item
85
execGetter _   _ _    FieldUnknown          = rsUnknown
86

    
87
-- * Main query execution
88

    
89
-- | Helper to build the list of requested fields. This transforms the
90
-- list of string fields to a list of field defs and getters, with
91
-- some of them possibly being unknown fields.
92
getSelectedFields :: FieldMap a b  -- ^ Defined fields
93
                  -> [String]      -- ^ Requested fields
94
                  -> FieldList a b -- ^ Selected fields
95
getSelectedFields defined =
96
  map (\name -> fromMaybe (mkUnknownFDef name) $ name `Map.lookup` defined)
97

    
98
-- | Collect live data from RPC query if enabled.
99
-- FIXME: Check which fields we actually need and possibly send empty
100
-- hvs/vgs if no info from hypervisor/volume group respectively
101
-- is required
102
maybeCollectLiveData:: Bool -> ConfigData -> [Node] -> IO [(Node, NodeRuntime)]
103

    
104
maybeCollectLiveData False _ nodes =
105
  return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled"))
106

    
107
maybeCollectLiveData True cfg nodes = do
108
  let vgs = [clusterVolumeGroupName $ configCluster cfg]
109
      hvs = [getDefaultHypervisor cfg]
110
  executeRpcCall nodes (RpcCallNodeInfo vgs hvs)
111

    
112
-- | Check whether list of queried fields contains live fields.
113
needsLiveData :: [FieldGetter a b] -> Bool
114
needsLiveData = any (\getter -> case getter of
115
                     FieldRuntime _ -> True
116
                     _ -> False)
117

    
118
-- | Checks whether we have requested exactly some names. This is a
119
-- simple wrapper over 'requestedNames' and 'nameField'.
120
needsNames :: Query -> Maybe [FilterValue]
121
needsNames (Query kind _ qfilter) = requestedNames (nameField kind) qfilter
122

    
123
-- | Computes the name field for different query types.
124
nameField :: ItemType -> FilterField
125
nameField QRJob = "id"
126
nameField _     = "name"
127

    
128
-- | Extracts all quoted strings from a list, ignoring the
129
-- 'NumericValue' entries.
130
getAllQuotedStrings :: [FilterValue] -> [String]
131
getAllQuotedStrings =
132
  concatMap extractor
133
    where extractor (NumericValue _)   = []
134
          extractor (QuotedString val) = [val]
135

    
136
-- | Checks that we have either requested a valid set of names, or we
137
-- have a more complex filter.
138
getRequestedNames :: Query -> [String]
139
getRequestedNames qry =
140
  case needsNames qry of
141
    Just names -> getAllQuotedStrings names
142
    Nothing    -> []
143

    
144
-- | Main query execution function.
145
query :: ConfigData   -- ^ The current configuration
146
      -> Bool         -- ^ Whether to collect live data
147
      -> Query        -- ^ The query (item, fields, filter)
148
      -> IO (Result QueryResult) -- ^ Result
149
query cfg live qry = queryInner cfg live qry $ getRequestedNames qry
150

    
151
-- | Inner query execution function.
152
queryInner :: ConfigData   -- ^ The current configuration
153
           -> Bool         -- ^ Whether to collect live data
154
           -> Query        -- ^ The query (item, fields, filter)
155
           -> [String]     -- ^ Requested names
156
           -> IO (Result QueryResult) -- ^ Result
157

    
158
queryInner cfg live (Query QRNode fields qfilter) wanted = runResultT $ do
159
  cfilter <- resultT $ compileFilter nodeFieldsMap qfilter
160
  let selected = getSelectedFields nodeFieldsMap fields
161
      (fdefs, fgetters) = unzip selected
162
      live' = live && needsLiveData fgetters
163
  nodes <- resultT $ case wanted of
164
             [] -> Ok . niceSortKey nodeName .
165
                   Map.elems . fromContainer $ configNodes cfg
166
             _  -> mapM (getNode cfg) wanted
167
  -- runs first pass of the filter, without a runtime context; this
168
  -- will limit the nodes that we'll contact for runtime data
169
  fnodes <- resultT $ filterM (\n -> evaluateFilter cfg Nothing n cfilter) nodes
170
  -- here we would run the runtime data gathering, then filter again
171
  -- the nodes, based on existing runtime data
172
  nruntimes <- lift $ maybeCollectLiveData live' cfg fnodes
173
  let fdata = map (\(node, nrt) -> map (execGetter cfg nrt node) fgetters)
174
              nruntimes
175
  return QueryResult { qresFields = fdefs, qresData = fdata }
176

    
177
queryInner cfg _ (Query QRGroup fields qfilter) wanted = return $ do
178
  cfilter <- compileFilter groupFieldsMap qfilter
179
  let selected = getSelectedFields groupFieldsMap fields
180
      (fdefs, fgetters) = unzip selected
181
  groups <- case wanted of
182
              [] -> Ok . niceSortKey groupName .
183
                    Map.elems . fromContainer $ configNodegroups cfg
184
              _  -> mapM (getGroup cfg) wanted
185
  -- there is no live data for groups, so filtering is much simpler
186
  fgroups <- filterM (\n -> evaluateFilter cfg Nothing n cfilter) groups
187
  let fdata = map (\node ->
188
                       map (execGetter cfg GroupRuntime node) fgetters) fgroups
189
  return QueryResult {qresFields = fdefs, qresData = fdata }
190

    
191
queryInner _ _ (Query qkind _ _) _ =
192
  return . Bad $ "Query '" ++ show qkind ++ "' not supported"
193

    
194
-- | Helper for 'queryFields'.
195
fieldsExtractor :: FieldMap a b -> [FilterField] -> QueryFieldsResult
196
fieldsExtractor fieldsMap fields =
197
  let selected = if null fields
198
                   then map snd $ Map.toAscList fieldsMap
199
                   else getSelectedFields fieldsMap fields
200
  in QueryFieldsResult (map fst selected)
201

    
202
-- | Query fields call.
203
queryFields :: QueryFields -> Result QueryFieldsResult
204
queryFields (QueryFields QRNode fields) =
205
  Ok $ fieldsExtractor nodeFieldsMap fields
206

    
207
queryFields (QueryFields QRGroup fields) =
208
  Ok $ fieldsExtractor groupFieldsMap fields
209

    
210
queryFields (QueryFields qkind _) =
211
  Bad $ "QueryFields '" ++ show qkind ++ "' not supported"