1 {-| Implementation of the Ganeti Query2 functionality.
7 Copyright (C) 2012 Google Inc.
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.
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.
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
28 TODO: problems with the current model:
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
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
47 module Ganeti.Query.Query
53 import Control.Monad (filterM)
54 import Data.Maybe (fromMaybe)
55 import qualified Data.Map as Map
57 import Ganeti.BasicTypes
59 import Ganeti.Query.Language
60 import Ganeti.Query.Common
61 import Ganeti.Query.Filter
62 import Ganeti.Query.Types
63 import Ganeti.Query.Node
64 import Ganeti.Query.Group
69 -- | Builds an unknown field definition.
70 mkUnknownFDef :: String -> FieldData a b
72 ( FieldDefinition name name QFTUnknown ("Unknown field '" ++ name ++ "'")
75 -- | Runs a field getter on the existing contexts.
76 execGetter :: ConfigData -> b -> a -> FieldGetter a b -> ResultEntry
77 execGetter _ _ item (FieldSimple getter) = getter item
78 execGetter cfg _ item (FieldConfig getter) = getter cfg item
79 execGetter _ rt item (FieldRuntime getter) = getter rt item
80 execGetter _ _ _ FieldUnknown = rsUnknown
82 -- * Main query execution
84 -- | Helper to build the list of requested fields. This transforms the
85 -- list of string fields to a list of field defs and getters, with
86 -- some of them possibly being unknown fields.
87 getSelectedFields :: FieldMap a b -- ^ Defined fields
88 -> [String] -- ^ Requested fields
89 -> FieldList a b -- ^ Selected fields
90 getSelectedFields defined =
91 map (\name -> fromMaybe (mkUnknownFDef name) $ name `Map.lookup` defined)
93 -- | Main query execution function.
94 query :: ConfigData -- ^ The current configuration
95 -> Bool -- ^ Whether to collect live data
96 -> Query -- ^ The query (item, fields, filter)
97 -> IO (Result QueryResult) -- ^ Result
99 query cfg _ (Query QRNode fields qfilter) = return $ do
100 cfilter <- compileFilter nodeFieldsMap qfilter
101 let selected = getSelectedFields nodeFieldsMap fields
102 (fdefs, fgetters) = unzip selected
103 nodes = Map.elems . fromContainer $ configNodes cfg
104 -- runs first pass of the filter, without a runtime context; this
105 -- will limit the nodes that we'll contact for runtime data
106 fnodes <- filterM (\n -> evaluateFilter cfg Nothing n cfilter)
108 -- here we would run the runtime data gathering, then filter again
109 -- the nodes, based on existing runtime data
110 let fdata = map (\node -> map (execGetter cfg NodeRuntime node) fgetters)
112 return QueryResult { qresFields = fdefs, qresData = fdata }
114 query cfg _ (Query QRGroup fields qfilter) = return $ do
115 -- FIXME: want_diskparams is defaulted to false and not taken as parameter
116 -- This is because the type for DiskParams is right now too generic for merges
117 -- (or else I cannot see how to do this with curent implementation)
118 cfilter <- compileFilter groupFieldsMap qfilter
119 let selected = getSelectedFields groupFieldsMap fields
120 (fdefs, fgetters) = unzip selected
121 groups = Map.elems . fromContainer $ configNodegroups cfg
122 -- there is no live data for groups, so filtering is much simpler
123 fgroups <- filterM (\n -> evaluateFilter cfg Nothing n cfilter) groups
124 let fdata = map (\node ->
125 map (execGetter cfg GroupRuntime node) fgetters) fgroups
126 return QueryResult {qresFields = fdefs, qresData = fdata }
128 query _ _ (Query qkind _ _) =
129 return . Bad $ "Query '" ++ show qkind ++ "' not supported"
131 -- | Query fields call.
132 -- FIXME: Looks generic enough to use a typeclass
133 queryFields :: QueryFields -> Result QueryFieldsResult
134 queryFields (QueryFields QRNode fields) =
135 let selected = if null fields
136 then map snd $ Map.toAscList nodeFieldsMap
137 else getSelectedFields nodeFieldsMap fields
138 in Ok $ QueryFieldsResult (map fst selected)
140 queryFields (QueryFields QRGroup fields) =
141 let selected = if null fields
142 then map snd $ Map.toAscList groupFieldsMap
143 else getSelectedFields groupFieldsMap fields
144 in Ok $ QueryFieldsResult (map fst selected)
147 queryFields (QueryFields qkind _) =
148 Bad $ "QueryFields '" ++ show qkind ++ "' not supported"