module Ganeti.Query.Query
( query
, queryFields
, queryCompat
, getRequestedNames
, nameField
, NoDataRuntime
, uuidField
) where
import Control.Arrow ((&&&))
import Control.DeepSeq
import Control.Monad (filterM, foldM, liftM, unless)
import Control.Monad.IO.Class
import Control.Monad.Trans (lift)
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.Foldable as Foldable
import Data.List (intercalate, nub, find)
import Data.Maybe (fromMaybe)
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Text.JSON as J
import Ganeti.BasicTypes
import Ganeti.Config
import Ganeti.Errors
import Ganeti.JQueue
import Ganeti.JSON (Container, GenericContainer(..))
import Ganeti.Locking.Allocation (OwnerState, LockRequest(..), OwnerState(..))
import Ganeti.Locking.Locks (GanetiLocks, ClientId, lockName)
import Ganeti.Logging
import Ganeti.Objects
import Ganeti.Query.Common
import qualified Ganeti.Query.Export as Export
import qualified Ganeti.Query.FilterRules as FilterRules
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.THH.HsRPC (runRpcClient)
import Ganeti.Types
import Ganeti.Utils
import Ganeti.WConfd.Client (getWConfdClient, listLocksWaitingStatus)
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 <- toError $ compileFilter fieldsMap qfilter
let allfields = (++) fields . filter (not . (`elem` fields))
. ordNub $ filterArguments qfilter
count = length fields
selected = getSelectedFields fieldsMap allfields
(fdefs, fgetters, _) = unzip3 selected
live' = live && needsLiveData fgetters
objects <- toError $ case wanted of
[] -> Ok . niceSortKey nameFn .
Foldable.toList $ configFn cfg
_ -> mapM (getFn cfg) wanted
fobjects <- toError $
filterM (\n -> evaluateQueryFilter cfg Nothing n cfilter) objects
runtimes <- (case collector of
CollectorSimple collFn -> lift $ collFn live' cfg fobjects
CollectorFieldAware collFn -> lift $ collFn live' cfg allfields fobjects)
>>= (toError . filterM (\(obj, runtime) ->
evaluateQueryFilter cfg (Just runtime) obj cfilter))
let fdata = map (\(obj, runtime) ->
map (execGetter cfg runtime obj) fgetters)
runtimes
return QueryResult { qresFields = take count fdefs
, qresData = map (take count) fdata }
recollectLocksData :: ( [(GanetiLocks, [(ClientId, OwnerState)])]
, [(Integer, ClientId, [LockRequest GanetiLocks])]
)
-> Bool -> ConfigData -> [String]
-> IO [(String, Locks.RuntimeData)]
recollectLocksData (allLocks, pending) _ _ =
let getPending lock = pending >>= \(_, cid, req) ->
let req' = filter ((==) lock . lockName . lockAffected) req
in case () of
_ | any ((==) (Just OwnExclusive) . lockRequestType) req'
-> [(cid, OwnExclusive)]
_ | any ((==) (Just OwnShared) . lockRequestType) req'
-> [(cid, OwnShared)]
_ -> []
lookuplock lock = (,) lock
. maybe ([], getPending lock)
(\(_, c) -> (c, getPending lock))
. find ((==) lock . lockName . fst)
$ allLocks
in return . map lookuplock
query :: ConfigData
-> Bool
-> Query
-> IO (ErrorResult QueryResult)
query cfg live (Query (ItemTypeLuxi QRJob) fields qfilter) =
queryJobs cfg live fields qfilter
query cfg live (Query (ItemTypeLuxi QRLock) fields qfilter) = runResultT $ do
unless live (failError "Locks can only be queried live")
cl <- liftIO $ do
socketpath <- defaultWConfdSocket
getWConfdClient socketpath
livedata <- runRpcClient listLocksWaitingStatus cl
logDebug $ "Live state of all locks is " ++ show livedata
let allLocks = Set.toList . Set.unions
$ (Set.fromList . map fst $ fst livedata)
: map (\(_, _, req) -> Set.fromList $ map lockAffected req)
(snd livedata)
answer <- liftIO $ genericQuery
Locks.fieldsMap
(CollectorSimple $ recollectLocksData livedata)
id
(const . GenericContainer . Map.fromList
. map ((UTF8.fromString &&& id) . lockName) $ allLocks)
(const Ok)
cfg live fields qfilter []
toError 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)
(fromMaybe "" . 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 cfg live (Query (ItemTypeLuxi QRFilter) fields qfilter) wanted =
genericQuery FilterRules.fieldsMap (CollectorSimple dummyCollectLiveData)
uuidOf configFilters getFilterRule 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
wanted_names <- toErrorStr $ getRequestedJobIDs qfilter
rjids <- case wanted_names of
[] | live -> do
let want_arch = Query.Job.wantArchived fields
jobIDs <-
withErrorT (BlockDeviceError .
(++) "Unable to fetch the job list: " . show) $
liftIO (determineJobDirectories rootdir want_arch)
>>= ResultT . getJobIDs
return $ sortJobIDs jobIDs
v -> return v
cfilter <- toError $ 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 <- toError $
filterM (\jid -> evaluateQueryFilter 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 <- toError $ evaluateQueryFilter 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 (ItemTypeLuxi QRFilter) fields) =
Ok $ fieldsExtractor FilterRules.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