module Ganeti.Query.Query
( query
, queryFields
, queryCompat
, getRequestedNames
, nameField
, NoDataRuntime
, uuidField
) where
import Control.DeepSeq
import Control.Monad (filterM, foldM, liftM)
import Control.Monad.Trans (lift)
import qualified Data.Foldable as Foldable
import Data.List (intercalate, nub)
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.Logging
import qualified Ganeti.Luxi as L
import Ganeti.Objects
import Ganeti.Query.Common
import qualified Ganeti.Query.Export as Export
import Ganeti.Query.Filter
import qualified Ganeti.Query.Instance as Instance
import qualified Ganeti.Query.Job as Query.Job
import qualified Ganeti.Query.Group as Group
import Ganeti.Query.Language
import qualified Ganeti.Query.Locks as Locks
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
data CollectorType a b
= CollectorSimple (Bool -> ConfigData -> [a] -> IO [(a, b)])
| CollectorFieldAware (Bool -> ConfigData -> [String] -> [a] -> IO [(a, b)])
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 cfg rt item (FieldConfigRuntime getter) = getter cfg 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 ->
liftM nub $
mapM (\e -> case e of
QuotedString s -> makeJobIdS s
NumericValue i -> makeJobId $ fromIntegral i
) vals
genericQuery :: FieldMap a b
-> CollectorType 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 <- case collector of
CollectorSimple collFn -> lift $ collFn live' cfg fobjects
CollectorFieldAware collFn -> lift $ collFn live' cfg fields 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 _ live (Query (ItemTypeLuxi QRLock) fields qfilter) =
if not live
then return . Bad $ GenericError "Locks can only be queried live"
else do
socketpath <- defaultMasterSocket
logDebug $ "Forwarding live query on locks for " ++ show fields
++ ", " ++ show qfilter ++ " to " ++ socketpath
cl <- L.getLuxiClient socketpath
answer <- L.callMethod (L.Query (ItemTypeLuxi QRLock) fields qfilter) cl
return
. genericResult Bad
(either (Bad . GenericError
. (++) "Got unparsable answer from masterd: ")
Ok
. J.resultToEither . J.readJSON)
$ answer
query cfg live qry = queryInner cfg live qry $ getRequestedNames qry
dummyCollectLiveData :: Bool -> ConfigData -> [a] -> IO [(a, NoDataRuntime)]
dummyCollectLiveData _ _ = return . map (, NoDataRuntime)
queryInner :: ConfigData
-> Bool
-> Query
-> [String]
-> IO (ErrorResult QueryResult)
queryInner cfg live (Query (ItemTypeOpCode QRNode) fields qfilter) wanted =
genericQuery Node.fieldsMap (CollectorFieldAware Node.collectLiveData)
nodeName configNodes getNode cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRInstance) fields qfilter) wanted =
genericQuery Instance.fieldsMap (CollectorFieldAware Instance.collectLiveData)
instName configInstances getInstance cfg live fields qfilter
wanted
queryInner cfg live (Query (ItemTypeOpCode QRGroup) fields qfilter) wanted =
genericQuery Group.fieldsMap (CollectorSimple dummyCollectLiveData) groupName
configNodegroups getGroup cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRNetwork) fields qfilter) wanted =
genericQuery Network.fieldsMap (CollectorSimple dummyCollectLiveData)
(fromNonEmpty . networkName)
configNetworks getNetwork cfg live fields qfilter wanted
queryInner cfg live (Query (ItemTypeOpCode QRExport) fields qfilter) wanted =
genericQuery Export.fieldsMap (CollectorSimple 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 . niceSortKey fst $ Map.toList 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 (ItemTypeOpCode QRInstance) fields) =
Ok $ fieldsExtractor Instance.fieldsMap fields
queryFields (QueryFields (ItemTypeLuxi QRLock) fields) =
Ok $ fieldsExtractor Locks.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