module Ganeti.Query.Node
( Runtime
, fieldsMap
, collectLiveData
) where
import Control.Applicative
import Data.List
import Data.Maybe
import qualified Data.Map as Map
import qualified Text.JSON as J
import Ganeti.Config
import Ganeti.Common
import Ganeti.Objects
import Ganeti.JSON
import Ganeti.Rpc
import Ganeti.Types
import Ganeti.Query.Language
import Ganeti.Query.Common
import Ganeti.Query.Types
import Ganeti.Storage.Utils
import Ganeti.Utils (niceSort)
type Runtime = Either RpcError RpcResultNodeInfo
nodeLiveFieldsDefs :: [(FieldName, FieldTitle, FieldType, String, FieldDoc)]
nodeLiveFieldsDefs =
[ ("bootid", "BootID", QFTText, "bootid",
"Random UUID renewed for each system reboot, can be used\
\ for detecting reboots by tracking changes")
, ("cnodes", "CNodes", QFTNumber, "cpu_nodes",
"Number of NUMA domains on node (if exported by hypervisor)")
, ("cnos", "CNOs", QFTNumber, "cpu_dom0",
"Number of logical processors used by the node OS (dom0 for Xen)")
, ("csockets", "CSockets", QFTNumber, "cpu_sockets",
"Number of physical CPU sockets (if exported by hypervisor)")
, ("ctotal", "CTotal", QFTNumber, "cpu_total",
"Number of logical processors")
, ("dfree", "DFree", QFTUnit, "storage_free",
"Available storage space on storage unit")
, ("dtotal", "DTotal", QFTUnit, "storage_size",
"Total storage space on storage unit for instance disk allocation")
, ("spfree", "SpFree", QFTNumber, "spindles_free",
"Available spindles in volume group (exclusive storage only)")
, ("sptotal", "SpTotal", QFTNumber, "spindles_total",
"Total spindles in volume group (exclusive storage only)")
, ("mfree", "MFree", QFTUnit, "memory_free",
"Memory available for instance allocations")
, ("mnode", "MNode", QFTUnit, "memory_dom0",
"Amount of memory used by node (dom0 for Xen)")
, ("mtotal", "MTotal", QFTUnit, "memory_total",
"Total amount of memory of physical machine")
]
getAttrFromStorageInfo :: (J.JSON a) => (StorageInfo -> Maybe a)
-> Maybe StorageInfo -> J.JSValue
getAttrFromStorageInfo attr_fn (Just info) =
case attr_fn info of
Just val -> J.showJSON val
Nothing -> J.JSNull
getAttrFromStorageInfo _ Nothing = J.JSNull
isStorageInfoOfType :: StorageType -> StorageInfo -> Bool
isStorageInfoOfType stype sinfo = storageInfoType sinfo ==
storageTypeToRaw stype
getStorageInfoForDefault :: [StorageInfo] -> Maybe StorageInfo
getStorageInfoForDefault sinfos = listToMaybe $ filter
(not . isStorageInfoOfType StorageLvmPv) sinfos
getStorageInfoForType :: [StorageInfo] -> StorageType -> Maybe StorageInfo
getStorageInfoForType sinfos stype = listToMaybe $ filter
(isStorageInfoOfType stype) sinfos
nodeLiveFieldExtract :: FieldName -> RpcResultNodeInfo -> J.JSValue
nodeLiveFieldExtract "bootid" res =
J.showJSON $ rpcResNodeInfoBootId res
nodeLiveFieldExtract "cnodes" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuNodes
nodeLiveFieldExtract "cnos" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuDom0
nodeLiveFieldExtract "csockets" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuSockets
nodeLiveFieldExtract "ctotal" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuTotal
nodeLiveFieldExtract "dfree" res =
getAttrFromStorageInfo storageInfoStorageFree (getStorageInfoForDefault
(rpcResNodeInfoStorageInfo res))
nodeLiveFieldExtract "dtotal" res =
getAttrFromStorageInfo storageInfoStorageSize (getStorageInfoForDefault
(rpcResNodeInfoStorageInfo res))
nodeLiveFieldExtract "spfree" res =
getAttrFromStorageInfo storageInfoStorageFree (getStorageInfoForType
(rpcResNodeInfoStorageInfo res) StorageLvmPv)
nodeLiveFieldExtract "sptotal" res =
getAttrFromStorageInfo storageInfoStorageSize (getStorageInfoForType
(rpcResNodeInfoStorageInfo res) StorageLvmPv)
nodeLiveFieldExtract "mfree" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryFree
nodeLiveFieldExtract "mnode" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryDom0
nodeLiveFieldExtract "mtotal" res =
jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryTotal
nodeLiveFieldExtract _ _ = J.JSNull
nodeLiveRpcCall :: FieldName -> Runtime -> Node -> ResultEntry
nodeLiveRpcCall fname (Right res) _ =
case nodeLiveFieldExtract fname res of
J.JSNull -> rsNoData
x -> rsNormal x
nodeLiveRpcCall _ (Left err) _ =
ResultEntry (rpcErrorToStatus err) Nothing
nodeLiveFieldBuilder :: (FieldName, FieldTitle, FieldType, String, FieldDoc)
-> FieldData Node Runtime
nodeLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) =
( FieldDefinition fname ftitle ftype fdoc
, FieldRuntime $ nodeLiveRpcCall fname
, QffNormal)
nodeRoleDoc :: String
nodeRoleDoc =
"Node role; " ++
intercalate ", "
(map (\role ->
"\"" ++ nodeRoleToRaw role ++ "\" for " ++ roleDescription role)
(reverse [minBound..maxBound]))
getNodePower :: ConfigData -> Node -> ResultEntry
getNodePower cfg node =
case getNodeNdParams cfg node of
Nothing -> rsNoData
Just ndp -> if null (ndpOobProgram ndp)
then rsUnavail
else rsNormal (nodePowered node)
nodeFields :: FieldList Node Runtime
nodeFields =
[ (FieldDefinition "drained" "Drained" QFTBool "Whether node is drained",
FieldSimple (rsNormal . nodeDrained), QffNormal)
, (FieldDefinition "master_candidate" "MasterC" QFTBool
"Whether node is a master candidate",
FieldSimple (rsNormal . nodeMasterCandidate), QffNormal)
, (FieldDefinition "master_capable" "MasterCapable" QFTBool
"Whether node can become a master candidate",
FieldSimple (rsNormal . nodeMasterCapable), QffNormal)
, (FieldDefinition "name" "Node" QFTText "Node name",
FieldSimple (rsNormal . nodeName), QffHostname)
, (FieldDefinition "offline" "Offline" QFTBool
"Whether node is marked offline",
FieldSimple (rsNormal . nodeOffline), QffNormal)
, (FieldDefinition "vm_capable" "VMCapable" QFTBool
"Whether node can host instances",
FieldSimple (rsNormal . nodeVmCapable), QffNormal)
, (FieldDefinition "pip" "PrimaryIP" QFTText "Primary IP address",
FieldSimple (rsNormal . nodePrimaryIp), QffNormal)
, (FieldDefinition "sip" "SecondaryIP" QFTText "Secondary IP address",
FieldSimple (rsNormal . nodeSecondaryIp), QffNormal)
, (FieldDefinition "master" "IsMaster" QFTBool "Whether node is master",
FieldConfig (\cfg node ->
rsNormal (nodeUuid node ==
clusterMasterNode (configCluster cfg))),
QffNormal)
, (FieldDefinition "group" "Group" QFTText "Node group",
FieldConfig (\cfg node ->
rsMaybeNoData (groupName <$> getGroupOfNode cfg node)),
QffNormal)
, (FieldDefinition "group.uuid" "GroupUUID" QFTText "UUID of node group",
FieldSimple (rsNormal . nodeGroup), QffNormal)
, (FieldDefinition "ndparams" "NodeParameters" QFTOther
"Merged node parameters",
FieldConfig ((rsMaybeNoData .) . getNodeNdParams), QffNormal)
, (FieldDefinition "custom_ndparams" "CustomNodeParameters" QFTOther
"Custom node parameters",
FieldSimple (rsNormal . nodeNdparams), QffNormal)
, (FieldDefinition "pinst_cnt" "Pinst" QFTNumber
"Number of instances with this node as primary",
FieldConfig (\cfg -> rsNormal . getNumInstances fst cfg), QffNormal)
, (FieldDefinition "sinst_cnt" "Sinst" QFTNumber
"Number of instances with this node as secondary",
FieldConfig (\cfg -> rsNormal . getNumInstances snd cfg), QffNormal)
, (FieldDefinition "pinst_list" "PriInstances" QFTOther
"List of instances with this node as primary",
FieldConfig (\cfg -> rsNormal . niceSort . map instName . fst .
getNodeInstances cfg . nodeUuid), QffNormal)
, (FieldDefinition "sinst_list" "SecInstances" QFTOther
"List of instances with this node as secondary",
FieldConfig (\cfg -> rsNormal . niceSort . map instName . snd .
getNodeInstances cfg . nodeUuid), QffNormal)
, (FieldDefinition "role" "Role" QFTText nodeRoleDoc,
FieldConfig ((rsNormal .) . getNodeRole), QffNormal)
, (FieldDefinition "powered" "Powered" QFTBool
"Whether node is thought to be powered on",
FieldConfig getNodePower, QffNormal)
, (FieldDefinition "hv_state" "HypervisorState" QFTOther "Hypervisor state",
FieldSimple (const rsUnavail), QffNormal)
, (FieldDefinition "disk_state" "DiskState" QFTOther "Disk state",
FieldSimple (const rsUnavail), QffNormal)
] ++
map nodeLiveFieldBuilder nodeLiveFieldsDefs ++
map buildNdParamField allNDParamFields ++
timeStampFields ++
uuidFields "Node" ++
serialFields "Node" ++
tagsFields
getNumInstances :: (([Instance], [Instance]) -> [Instance])
-> ConfigData -> Node -> Int
getNumInstances get_fn cfg = length . get_fn . getNodeInstances cfg . nodeUuid
fieldsMap :: FieldMap Node Runtime
fieldsMap = fieldListToFieldMap nodeFields
rpcResultNodeBroken :: Node -> (Node, Runtime)
rpcResultNodeBroken node = (node, Left (RpcResultError "Broken configuration"))
storageFields :: [String]
storageFields = ["dtotal", "dfree", "spfree", "sptotal"]
hypervisorFields :: [String]
hypervisorFields = ["mnode", "mfree", "mtotal",
"cnodes", "csockets", "cnos", "ctotal"]
queryDomainRequired ::
[String]
-> [String]
-> Bool
queryDomainRequired domain_fields fields = any (`elem` fields) domain_fields
collectLiveData :: Bool
-> ConfigData
-> [String]
-> [Node]
-> IO [(Node, Runtime)]
collectLiveData False _ _ nodes =
return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled"))
collectLiveData True cfg fields nodes = do
let hvs = [getDefaultHypervisorSpec cfg |
queryDomainRequired hypervisorFields fields]
good_nodes = nodesWithValidConfig cfg nodes
storage_units = if queryDomainRequired storageFields fields
then getStorageUnitsOfNodes cfg good_nodes
else Map.fromList
(map (\n -> (nodeUuid n, [])) good_nodes)
rpcres <- executeRpcCall good_nodes (RpcCallNodeInfo storage_units hvs)
return $ fillUpList (fillPairFromMaybe rpcResultNodeBroken pickPairUnique)
nodes rpcres