module Ganeti.Query.Query
( query
, queryFields
, queryCompat
, getRequestedNames
, nameField
, uuidField
) where
import Control.DeepSeq
import Control.Monad (filterM, foldM)
import Control.Monad.Trans (lift)
import qualified Data.Foldable as Foldable
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
import qualified Data.Map as Map
import qualified Text.JSON as J
import Ganeti.BasicTypes
import Ganeti.Config
import Ganeti.Errors
import Ganeti.JQueue
import Ganeti.JSON
import Ganeti.Objects
import Ganeti.Query.Common
import qualified Ganeti.Query.Export as Export
import Ganeti.Query.Filter
import qualified Ganeti.Query.Job as Query.Job
import qualified Ganeti.Query.Group as Group
import Ganeti.Query.Language
import qualified Ganeti.Query.Network as Network
import qualified Ganeti.Query.Node as Node
import Ganeti.Query.Types
import Ganeti.Path
import Ganeti.Types
import Ganeti.Utils
mkUnknownFDef :: String -> FieldData a b
mkUnknownFDef name =
( FieldDefinition name name QFTUnknown ("Unknown field '" ++ name ++ "'")
, FieldUnknown
, QffNormal )
execGetter :: ConfigData -> b -> a -> FieldGetter a b -> ResultEntry
execGetter _ _ item (FieldSimple getter) = getter item
execGetter cfg _ item (FieldConfig getter) = getter cfg item
execGetter _ rt item (FieldRuntime getter) = getter rt item
execGetter _ _ _ FieldUnknown = rsUnknown
getSelectedFields :: FieldMap a b
-> [String]
-> FieldList a b
getSelectedFields defined =
map (\name -> fromMaybe (mkUnknownFDef name) $ name `Map.lookup` defined)
needsLiveData :: [FieldGetter a b] -> Bool
needsLiveData = any isRuntimeField
needsNames :: Query -> Maybe [FilterValue]
needsNames (Query kind _ qfilter) = requestedNames (nameField kind) qfilter
nameField :: ItemType -> FilterField
nameField (ItemTypeLuxi QRJob) = "id"
nameField (ItemTypeOpCode QRExport) = "node"
nameField _ = "name"
uuidField :: ItemType -> FilterField
uuidField (ItemTypeLuxi QRJob) = nameField (ItemTypeLuxi QRJob)
uuidField (ItemTypeOpCode QRExport) = nameField (ItemTypeOpCode QRExport)
uuidField _ = "uuid"
getAllQuotedStrings :: [FilterValue] -> [String]
getAllQuotedStrings =
concatMap extractor
where extractor (NumericValue _) = []
extractor (QuotedString val) = [val]
getRequestedNames :: Query -> [String]
getRequestedNames qry =
case needsNames qry of
Just names -> getAllQuotedStrings names
Nothing -> []
getRequestedJobIDs :: Filter FilterField -> Result [JobId]
getRequestedJobIDs qfilter =
case requestedNames (nameField (ItemTypeLuxi QRJob)) qfilter of
Nothing -> Ok []
Just [] -> Ok []
Just vals ->
mapM (\e -> case e of
QuotedString s -> makeJobIdS s
NumericValue i -> makeJobId $ fromIntegral i
) vals
genericQuery :: FieldMap a b
-> (Bool -> ConfigData -> [a] -> IO [(a, b)])
-> (a -> String)
-> (ConfigData -> Container a)
-> (ConfigData -> String -> ErrorResult a)
-> ConfigData
-> Bool
-> [String]
-> Filter FilterField
-> [String]
-> IO (ErrorResult QueryResult)
genericQuery fieldsMap collector nameFn configFn getFn cfg
live fields qfilter wanted =
runResultT $ do
cfilter <- resultT $ compileFilter fieldsMap qfilter
let selected = getSelectedFields fieldsMap fields
(fdefs, fgetters, _) = unzip3 selected
live' = live && needsLiveData fgetters
objects <- resultT $ case wanted of
[] -> Ok . niceSortKey nameFn .
Map.elems . fromContainer $ configFn cfg
_ -> mapM (getFn cfg) wanted
fobjects <- resultT $ filterM (\n -> evaluateFilter cfg Nothing n cfilter)
objects
runtimes <- lift $ collector live' cfg fobjects
let fdata = map (\(obj, runtime) ->
map (execGetter cfg runtime obj) fgetters)
runtimes
return QueryResult { qresFields = fdefs, qresData = fdata }
query :: ConfigData
-> Bool
-> Query
-> IO (ErrorResult QueryResult)
query cfg live (Query (ItemTypeLuxi QRJob) fields qfilter) =
queryJobs cfg live fields qfilter
query cfg live qry = queryInner cfg live qry $ getRequestedNames qry
queryInner :: ConfigData
-> Bool
-> Query
-> [String]
-> IO (ErrorResult QueryResult)
queryInner cfg live (Query (ItemTypeOpCode QRNode) fields qfilter) wanted =
genericQuery Node.fieldsMap Node.collectLiveData nodeName configNodes getNode
cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRGroup) fields qfilter) wanted =
genericQuery Group.fieldsMap Group.collectLiveData groupName configNodegroups
getGroup cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRNetwork) fields qfilter) wanted =
genericQuery Network.fieldsMap Network.collectLiveData
(fromNonEmpty . networkName)
configNetworks getNetwork cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRExport) fields qfilter) wanted =
genericQuery Export.fieldsMap Export.collectLiveData nodeName configNodes
getNode cfg live fields qfilter wanted
queryInner _ _ (Query qkind _ _) _ =
return . Bad . GenericError $ "Query '" ++ show qkind ++ "' not supported"
queryJobs :: ConfigData
-> Bool
-> [FilterField]
-> Filter FilterField
-> IO (ErrorResult QueryResult)
queryJobs cfg live fields qfilter =
runResultT $ do
rootdir <- lift queueDir
let wanted_names = getRequestedJobIDs qfilter
want_arch = Query.Job.wantArchived fields
rjids <- case wanted_names of
Bad msg -> resultT . Bad $ GenericError msg
Ok [] -> if live
then do
maybeJobIDs <-
lift (determineJobDirectories rootdir want_arch
>>= getJobIDs)
case maybeJobIDs of
Left e -> (resultT . Bad) . BlockDeviceError $
"Unable to fetch the job list: " ++ show e
Right jobIDs -> resultT . Ok $ sortJobIDs jobIDs
else return []
Ok v -> resultT $ Ok v
cfilter <- resultT $ compileFilter Query.Job.fieldsMap qfilter
let selected = getSelectedFields Query.Job.fieldsMap fields
(fdefs, fgetters, _) = unzip3 selected
(_, filtergetters, _) = unzip3 . getSelectedFields Query.Job.fieldsMap
$ Foldable.toList qfilter
live' = live && needsLiveData (fgetters ++ filtergetters)
disabled_data = Bad "live data disabled"
jids <- resultT $
filterM (\jid -> evaluateFilter cfg Nothing jid cfilter) rjids
qdir <- lift queueDir
fdata <- foldM
(\lst jid -> do
job <- lift $ if live'
then loadJobFromDisk qdir True jid
else return disabled_data
pass <- resultT $ evaluateFilter cfg (Just job) jid cfilter
let nlst = if pass
then let row = map (execGetter cfg job jid) fgetters
in rnf row `seq` row:lst
else lst
return $! nlst
) [] jids
return QueryResult { qresFields = fdefs, qresData = reverse fdata }
fieldsExtractor :: FieldMap a b -> [FilterField] -> QueryFieldsResult
fieldsExtractor fieldsMap fields =
let selected = if null fields
then map snd $ Map.toAscList fieldsMap
else getSelectedFields fieldsMap fields
in QueryFieldsResult (map (\(defs, _, _) -> defs) selected)
queryFields :: QueryFields -> ErrorResult QueryFieldsResult
queryFields (QueryFields (ItemTypeOpCode QRNode) fields) =
Ok $ fieldsExtractor Node.fieldsMap fields
queryFields (QueryFields (ItemTypeOpCode QRGroup) fields) =
Ok $ fieldsExtractor Group.fieldsMap fields
queryFields (QueryFields (ItemTypeOpCode QRNetwork) fields) =
Ok $ fieldsExtractor Network.fieldsMap fields
queryFields (QueryFields (ItemTypeLuxi QRJob) fields) =
Ok $ fieldsExtractor Query.Job.fieldsMap fields
queryFields (QueryFields (ItemTypeOpCode QRExport) fields) =
Ok $ fieldsExtractor Export.fieldsMap fields
queryFields (QueryFields qkind _) =
Bad . GenericError $ "QueryFields '" ++ show qkind ++ "' not supported"
queryCompat :: QueryResult -> ErrorResult [[J.JSValue]]
queryCompat (QueryResult fields qrdata) =
case map fdefName $ filter ((== QFTUnknown) . fdefKind) fields of
[] -> Ok $ map (map (maybe J.JSNull J.showJSON . rentryValue)) qrdata
unknown -> Bad $ OpPrereqError ("Unknown output fields selected: " ++
intercalate ", " unknown) ECodeInval