module Ganeti.Query.Query
( query
, queryFields
, queryCompat
, getRequestedNames
, nameField
) where
import Control.DeepSeq
import Control.Monad (filterM, foldM)
import Control.Monad.Trans (lift)
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 Ganeti.Query.Filter
import qualified Ganeti.Query.Job as Query.Job
import Ganeti.Query.Group
import Ganeti.Query.Language
import Ganeti.Query.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 _ = "name"
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
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 =
runResultT $ do
cfilter <- resultT $ compileFilter nodeFieldsMap qfilter
let selected = getSelectedFields nodeFieldsMap fields
(fdefs, fgetters, _) = unzip3 selected
live' = live && needsLiveData fgetters
nodes <- resultT $ case wanted of
[] -> Ok . niceSortKey nodeName .
Map.elems . fromContainer $ configNodes cfg
_ -> mapM (getNode cfg) wanted
fnodes <- resultT $ filterM (\n -> evaluateFilter cfg Nothing n cfilter)
nodes
nruntimes <- lift $ maybeCollectLiveData live' cfg fnodes
let fdata = map (\(node, nrt) -> map (execGetter cfg nrt node) fgetters)
nruntimes
return QueryResult { qresFields = fdefs, qresData = fdata }
queryInner cfg _ (Query (ItemTypeOpCode QRGroup) fields qfilter) wanted =
return $ do
cfilter <- compileFilter groupFieldsMap qfilter
let selected = getSelectedFields groupFieldsMap fields
(fdefs, fgetters, _) = unzip3 selected
groups <- case wanted of
[] -> Ok . niceSortKey groupName .
Map.elems . fromContainer $ configNodegroups cfg
_ -> mapM (getGroup cfg) wanted
fgroups <- filterM (\n -> evaluateFilter cfg Nothing n cfilter) groups
let fdata = map (\node ->
map (execGetter cfg GroupRuntime node) fgetters) fgroups
return QueryResult {qresFields = fdefs, qresData = fdata }
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
live' = live && needsLiveData fgetters
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 nodeFieldsMap fields
queryFields (QueryFields (ItemTypeOpCode QRGroup) fields) =
Ok $ fieldsExtractor groupFieldsMap fields
queryFields (QueryFields (ItemTypeLuxi QRJob) fields) =
Ok $ fieldsExtractor Query.Job.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