Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Query / Node.hs @ 212b66c3

History | View | Annotate | Download (10.7 kB)

1
{-| Implementation of the Ganeti Query2 node queries.
2

    
3
 -}
4

    
5
{-
6

    
7
Copyright (C) 2012, 2013 Google Inc.
8

    
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13

    
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
General Public License for more details.
18

    
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
02110-1301, USA.
23

    
24
-}
25

    
26
module Ganeti.Query.Node
27
  ( Runtime
28
  , fieldsMap
29
  , collectLiveData
30
  ) where
31

    
32
import Control.Applicative
33
import Data.List
34
import Data.Maybe
35
import qualified Data.Map as Map
36
import qualified Text.JSON as J
37

    
38
import Ganeti.Config
39
import Ganeti.Common
40
import Ganeti.Objects
41
import Ganeti.JSON
42
import Ganeti.Rpc
43
import Ganeti.Types
44
import Ganeti.Query.Language
45
import Ganeti.Query.Common
46
import Ganeti.Query.Types
47
import Ganeti.Storage.Utils
48
import Ganeti.Utils (niceSort)
49

    
50
-- | Runtime is the resulting type for NodeInfo call.
51
type Runtime = Either RpcError RpcResultNodeInfo
52

    
53
-- | List of node live fields.
54
nodeLiveFieldsDefs :: [(FieldName, FieldTitle, FieldType, String, FieldDoc)]
55
nodeLiveFieldsDefs =
56
  [ ("bootid", "BootID", QFTText, "bootid",
57
     "Random UUID renewed for each system reboot, can be used\
58
     \ for detecting reboots by tracking changes")
59
  , ("cnodes", "CNodes", QFTNumber, "cpu_nodes",
60
     "Number of NUMA domains on node (if exported by hypervisor)")
61
  , ("cnos", "CNOs", QFTNumber, "cpu_dom0",
62
     "Number of logical processors used by the node OS (dom0 for Xen)")
63
  , ("csockets", "CSockets", QFTNumber, "cpu_sockets",
64
     "Number of physical CPU sockets (if exported by hypervisor)")
65
  , ("ctotal", "CTotal", QFTNumber, "cpu_total",
66
     "Number of logical processors")
67
  , ("dfree", "DFree", QFTUnit, "storage_free",
68
     "Available storage space on storage unit")
69
  , ("dtotal", "DTotal", QFTUnit, "storage_size",
70
     "Total storage space on storage unit for instance disk allocation")
71
  , ("spfree", "SpFree", QFTNumber, "spindles_free",
72
     "Available spindles in volume group (exclusive storage only)")
73
  , ("sptotal", "SpTotal", QFTNumber, "spindles_total",
74
     "Total spindles in volume group (exclusive storage only)")
75
  , ("mfree", "MFree", QFTUnit, "memory_free",
76
     "Memory available for instance allocations")
77
  , ("mnode", "MNode", QFTUnit, "memory_dom0",
78
     "Amount of memory used by node (dom0 for Xen)")
79
  , ("mtotal", "MTotal", QFTUnit, "memory_total",
80
     "Total amount of memory of physical machine")
81
  ]
82

    
83
-- | Map each name to a function that extracts that value from
84
-- the RPC result.
85
nodeLiveFieldExtract :: FieldName -> RpcResultNodeInfo -> J.JSValue
86
nodeLiveFieldExtract "bootid" res =
87
  J.showJSON $ rpcResNodeInfoBootId res
88
nodeLiveFieldExtract "cnodes" res =
89
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuNodes
90
nodeLiveFieldExtract "cnos" res =
91
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuDom0
92
nodeLiveFieldExtract "csockets" res =
93
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuSockets
94
nodeLiveFieldExtract "ctotal" res =
95
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuTotal
96
nodeLiveFieldExtract "dfree" res =
97
  getMaybeJsonHead (rpcResNodeInfoStorageInfo res) storageInfoStorageFree
98
nodeLiveFieldExtract "dtotal" res =
99
  getMaybeJsonHead (rpcResNodeInfoStorageInfo res) storageInfoStorageSize
100
nodeLiveFieldExtract "spfree" res =
101
  getMaybeJsonElem (rpcResNodeInfoStorageInfo res) 1 storageInfoStorageFree
102
nodeLiveFieldExtract "sptotal" res =
103
  getMaybeJsonElem (rpcResNodeInfoStorageInfo res) 1 storageInfoStorageSize
104
nodeLiveFieldExtract "mfree" res =
105
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryFree
106
nodeLiveFieldExtract "mnode" res =
107
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryDom0
108
nodeLiveFieldExtract "mtotal" res =
109
  jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryTotal
110
nodeLiveFieldExtract _ _ = J.JSNull
111

    
112
-- | Helper for extracting field from RPC result.
113
nodeLiveRpcCall :: FieldName -> Runtime -> Node -> ResultEntry
114
nodeLiveRpcCall fname (Right res) _ =
115
  case nodeLiveFieldExtract fname res of
116
    J.JSNull -> rsNoData
117
    x -> rsNormal x
118
nodeLiveRpcCall _ (Left err) _ =
119
    ResultEntry (rpcErrorToStatus err) Nothing
120

    
121
-- | Builder for node live fields.
122
nodeLiveFieldBuilder :: (FieldName, FieldTitle, FieldType, String, FieldDoc)
123
                     -> FieldData Node Runtime
124
nodeLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) =
125
  ( FieldDefinition fname ftitle ftype fdoc
126
  , FieldRuntime $ nodeLiveRpcCall fname
127
  , QffNormal)
128

    
129
-- | The docstring for the node role. Note that we use 'reverse' in
130
-- order to keep the same order as Python.
131
nodeRoleDoc :: String
132
nodeRoleDoc =
133
  "Node role; " ++
134
  intercalate ", "
135
   (map (\role ->
136
          "\"" ++ nodeRoleToRaw role ++ "\" for " ++ roleDescription role)
137
   (reverse [minBound..maxBound]))
138

    
139
-- | Get node powered status.
140
getNodePower :: ConfigData -> Node -> ResultEntry
141
getNodePower cfg node =
142
  case getNodeNdParams cfg node of
143
    Nothing -> rsNoData
144
    Just ndp -> if null (ndpOobProgram ndp)
145
                  then rsUnavail
146
                  else rsNormal (nodePowered node)
147

    
148
-- | List of all node fields.
149
nodeFields :: FieldList Node Runtime
150
nodeFields =
151
  [ (FieldDefinition "drained" "Drained" QFTBool "Whether node is drained",
152
     FieldSimple (rsNormal . nodeDrained), QffNormal)
153
  , (FieldDefinition "master_candidate" "MasterC" QFTBool
154
       "Whether node is a master candidate",
155
     FieldSimple (rsNormal . nodeMasterCandidate), QffNormal)
156
  , (FieldDefinition "master_capable" "MasterCapable" QFTBool
157
       "Whether node can become a master candidate",
158
     FieldSimple (rsNormal . nodeMasterCapable), QffNormal)
159
  , (FieldDefinition "name" "Node" QFTText "Node name",
160
     FieldSimple (rsNormal . nodeName), QffHostname)
161
  , (FieldDefinition "offline" "Offline" QFTBool
162
       "Whether node is marked offline",
163
     FieldSimple (rsNormal . nodeOffline), QffNormal)
164
  , (FieldDefinition "vm_capable" "VMCapable" QFTBool
165
       "Whether node can host instances",
166
     FieldSimple (rsNormal . nodeVmCapable), QffNormal)
167
  , (FieldDefinition "pip" "PrimaryIP" QFTText "Primary IP address",
168
     FieldSimple (rsNormal . nodePrimaryIp), QffNormal)
169
  , (FieldDefinition "sip" "SecondaryIP" QFTText "Secondary IP address",
170
     FieldSimple (rsNormal . nodeSecondaryIp), QffNormal)
171
  , (FieldDefinition "master" "IsMaster" QFTBool "Whether node is master",
172
     FieldConfig (\cfg node ->
173
                    rsNormal (nodeName node ==
174
                              clusterMasterNode (configCluster cfg))),
175
     QffNormal)
176
  , (FieldDefinition "group" "Group" QFTText "Node group",
177
     FieldConfig (\cfg node ->
178
                    rsMaybeNoData (groupName <$> getGroupOfNode cfg node)),
179
     QffNormal)
180
  , (FieldDefinition "group.uuid" "GroupUUID" QFTText "UUID of node group",
181
     FieldSimple (rsNormal . nodeGroup), QffNormal)
182
  ,  (FieldDefinition "ndparams" "NodeParameters" QFTOther
183
        "Merged node parameters",
184
      FieldConfig ((rsMaybeNoData .) . getNodeNdParams), QffNormal)
185
  , (FieldDefinition "custom_ndparams" "CustomNodeParameters" QFTOther
186
                       "Custom node parameters",
187
     FieldSimple (rsNormal . nodeNdparams), QffNormal)
188
  -- FIXME: the below could be generalised a bit, like in Python
189
  , (FieldDefinition "pinst_cnt" "Pinst" QFTNumber
190
       "Number of instances with this node as primary",
191
     FieldConfig (\cfg ->
192
                    rsNormal . length . fst . getNodeInstances cfg . nodeUuid),
193
     QffNormal)
194
  , (FieldDefinition "sinst_cnt" "Sinst" QFTNumber
195
       "Number of instances with this node as secondary",
196
     FieldConfig (\cfg ->
197
                    rsNormal . length . snd . getNodeInstances cfg . nodeUuid),
198
     QffNormal)
199
  , (FieldDefinition "pinst_list" "PriInstances" QFTOther
200
       "List of instances with this node as primary",
201
     FieldConfig (\cfg -> rsNormal . niceSort . map instName . fst .
202
                          getNodeInstances cfg . nodeUuid), QffNormal)
203
  , (FieldDefinition "sinst_list" "SecInstances" QFTOther
204
       "List of instances with this node as secondary",
205
     FieldConfig (\cfg -> rsNormal . niceSort . map instName . snd .
206
                          getNodeInstances cfg . nodeUuid), QffNormal)
207
  , (FieldDefinition "role" "Role" QFTText nodeRoleDoc,
208
     FieldConfig ((rsNormal .) . getNodeRole), QffNormal)
209
  , (FieldDefinition "powered" "Powered" QFTBool
210
       "Whether node is thought to be powered on",
211
     FieldConfig getNodePower, QffNormal)
212
  -- FIXME: the two fields below are incomplete in Python, part of the
213
  -- non-implemented node resource model; they are declared just for
214
  -- parity, but are not functional
215
  , (FieldDefinition "hv_state" "HypervisorState" QFTOther "Hypervisor state",
216
     FieldSimple (const rsUnavail), QffNormal)
217
  , (FieldDefinition "disk_state" "DiskState" QFTOther "Disk state",
218
     FieldSimple (const rsUnavail), QffNormal)
219
  ] ++
220
  map nodeLiveFieldBuilder nodeLiveFieldsDefs ++
221
  map buildNdParamField allNDParamFields ++
222
  timeStampFields ++
223
  uuidFields "Node" ++
224
  serialFields "Node" ++
225
  tagsFields
226

    
227
-- | The node fields map.
228
fieldsMap :: FieldMap Node Runtime
229
fieldsMap =
230
  Map.fromList $ map (\v@(f, _, _) -> (fdefName f, v)) nodeFields
231

    
232
-- | Create an RPC result for a broken node
233
rpcResultNodeBroken :: Node -> (Node, Runtime)
234
rpcResultNodeBroken node = (node, Left (RpcResultError "Broken configuration"))
235

    
236
-- | Collect live data from RPC query if enabled.
237
--
238
-- FIXME: Check which fields we actually need and possibly send empty
239
-- hvs\/vgs if no info from hypervisor\/volume group respectively is
240
-- required
241
collectLiveData:: Bool -> ConfigData -> [Node] -> IO [(Node, Runtime)]
242
collectLiveData False _ nodes =
243
  return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled"))
244
collectLiveData True cfg nodes = do
245
  let hvs = [getDefaultHypervisorSpec cfg]
246
      good_nodes = nodesWithValidConfig cfg nodes
247
      storage_units = getStorageUnitsOfNodes cfg good_nodes
248
  rpcres <- executeRpcCall good_nodes (RpcCallNodeInfo storage_units hvs)
249
  return $ fillUpList (fillPairFromMaybe rpcResultNodeBroken pickPairUnique)
250
      nodes rpcres
251

    
252
-- | Looks up the default hypervisor and it's hvparams
253
getDefaultHypervisorSpec :: ConfigData -> (Hypervisor, HvParams)
254
getDefaultHypervisorSpec cfg = (hv, getHvParamsFromCluster cfg hv)
255
  where hv = getDefaultHypervisor cfg
256

    
257
-- | Looks up the cluster's hvparams of the given hypervisor
258
getHvParamsFromCluster :: ConfigData -> Hypervisor -> HvParams
259
getHvParamsFromCluster cfg hv =
260
  fromMaybe (GenericContainer (Map.fromList []))
261
    (Map.lookup (hypervisorToRaw hv)
262
       (fromContainer (clusterHvparams (configCluster cfg))))