root / src / Ganeti / Query / Instance.hs @ 4e6f1cde
History | View | Annotate | Download (16.3 kB)
1 |
{-| Implementation of the Ganeti Query2 instance queries. |
---|---|
2 |
|
3 |
-} |
4 |
|
5 |
{- |
6 |
|
7 |
Copyright (C) 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.Instance |
27 |
( Runtime |
28 |
, fieldsMap |
29 |
, collectLiveData |
30 |
) where |
31 |
|
32 |
import Control.Applicative |
33 |
import Data.List |
34 |
import Data.Maybe |
35 |
import Data.Monoid |
36 |
import qualified Data.Map as Map |
37 |
import qualified Text.JSON as J |
38 |
|
39 |
import Ganeti.BasicTypes |
40 |
import Ganeti.Common |
41 |
import Ganeti.Config |
42 |
import qualified Ganeti.Constants as C |
43 |
import qualified Ganeti.ConstantUtils as C |
44 |
import Ganeti.Errors |
45 |
import Ganeti.JSON |
46 |
import Ganeti.Objects |
47 |
import Ganeti.Query.Common |
48 |
import Ganeti.Query.Language |
49 |
import Ganeti.Query.Types |
50 |
import Ganeti.Rpc |
51 |
import Ganeti.Storage.Utils |
52 |
import Ganeti.Types |
53 |
|
54 |
-- | The LiveInfo structure packs additional information beside the |
55 |
-- 'InstanceInfo'. We also need to know whether the instance information was |
56 |
-- found on the primary node, and encode this as a Bool. |
57 |
type LiveInfo = (InstanceInfo, Bool) |
58 |
|
59 |
-- | Runtime possibly containing the 'LiveInfo'. See the genericQuery function |
60 |
-- in the Query.hs file for an explanation of the terms used. |
61 |
type Runtime = Either RpcError (Maybe LiveInfo) |
62 |
|
63 |
-- | The instance fields map. |
64 |
fieldsMap :: FieldMap Instance Runtime |
65 |
fieldsMap = Map.fromList [(fdefName f, v) | v@(f, _, _) <- instanceFields] |
66 |
|
67 |
-- | The instance fields |
68 |
instanceFields :: FieldList Instance Runtime |
69 |
instanceFields = |
70 |
-- Simple fields |
71 |
[ (FieldDefinition "admin_state" "InstanceState" QFTText |
72 |
"Desired state of instance", |
73 |
FieldSimple (rsNormal . adminStateToRaw . instAdminState), QffNormal) |
74 |
, (FieldDefinition "admin_up" "Autostart" QFTBool |
75 |
"Desired state of instance", |
76 |
FieldSimple (rsNormal . (== AdminUp) . instAdminState), QffNormal) |
77 |
, (FieldDefinition "disk_template" "Disk_template" QFTText |
78 |
"Instance disk template", |
79 |
FieldSimple (rsNormal . instDiskTemplate), QffNormal) |
80 |
, (FieldDefinition "disks_active" "DisksActive" QFTBool |
81 |
"Desired state of instance disks", |
82 |
FieldSimple (rsNormal . instDisksActive), QffNormal) |
83 |
, (FieldDefinition "name" "Instance" QFTText |
84 |
"Instance name", |
85 |
FieldSimple (rsNormal . instName), QffHostname) |
86 |
, (FieldDefinition "hypervisor" "Hypervisor" QFTText |
87 |
"Hypervisor name", |
88 |
FieldSimple (rsNormal . instHypervisor), QffNormal) |
89 |
, (FieldDefinition "network_port" "Network_port" QFTOther |
90 |
"Instance network port if available (e.g. for VNC console)", |
91 |
FieldSimple (rsMaybeUnavail . instNetworkPort), QffNormal) |
92 |
, (FieldDefinition "os" "OS" QFTText |
93 |
"Operating system", |
94 |
FieldSimple (rsNormal . instOs), QffNormal) |
95 |
, (FieldDefinition "pnode" "Primary_node" QFTText |
96 |
"Primary node", |
97 |
FieldConfig getPrimaryNodeName, QffHostname) |
98 |
, (FieldDefinition "pnode.group" "PrimaryNodeGroup" QFTText |
99 |
"Primary node's group", |
100 |
FieldConfig getPrimaryNodeGroup, QffNormal) |
101 |
, (FieldDefinition "snodes" "Secondary_Nodes" QFTOther |
102 |
"Secondary nodes; usually this will just be one node", |
103 |
FieldConfig (getSecondaryNodeAttribute nodeName), QffNormal) |
104 |
, (FieldDefinition "snodes.group" "SecondaryNodesGroups" QFTOther |
105 |
"Node groups of secondary nodes", |
106 |
FieldConfig (getSecondaryNodeGroupAttribute groupName), QffNormal) |
107 |
, (FieldDefinition "snodes.group.uuid" "SecondaryNodesGroupsUUID" QFTOther |
108 |
"Node group UUIDs of secondary nodes", |
109 |
FieldConfig (getSecondaryNodeGroupAttribute groupUuid), QffNormal) |
110 |
] ++ |
111 |
|
112 |
-- Instance parameter fields, whole |
113 |
[ (FieldDefinition "hvparams" "HypervisorParameters" QFTOther |
114 |
"Hypervisor parameters (merged)", |
115 |
FieldConfig ((rsNormal .) . getFilledInstHvParams), QffNormal) |
116 |
, (FieldDefinition "beparams" "BackendParameters" QFTOther |
117 |
"Backend parameters (merged)", |
118 |
FieldConfig ((rsErrorNoData .) . getFilledInstBeParams), QffNormal) |
119 |
, (FieldDefinition "osparams" "OpSysParameters" QFTOther |
120 |
"Operating system parameters (merged)", |
121 |
FieldConfig ((rsNormal .) . getFilledInstOsParams), QffNormal) |
122 |
, (FieldDefinition "custom_hvparams" "CustomHypervisorParameters" QFTOther |
123 |
"Custom hypervisor parameters", |
124 |
FieldSimple (rsNormal . instHvparams), QffNormal) |
125 |
, (FieldDefinition "custom_beparams" "CustomBackendParameters" QFTOther |
126 |
"Custom backend parameters", |
127 |
FieldSimple (rsNormal . instBeparams), QffNormal) |
128 |
, (FieldDefinition "custom_osparams" "CustomOpSysParameters" QFTOther |
129 |
"Custom operating system parameters", |
130 |
FieldSimple (rsNormal . instOsparams), QffNormal) |
131 |
] ++ |
132 |
|
133 |
-- Instance parameter fields, generated |
134 |
map (buildBeParamField beParamGetter) allBeParamFields ++ |
135 |
map (buildHvParamField hvParamGetter) (C.toList C.hvsParameters) ++ |
136 |
|
137 |
-- Live fields using special getters |
138 |
[ (FieldDefinition "status" "Status" QFTText |
139 |
statusDocText, |
140 |
FieldConfigRuntime statusExtract, QffNormal) |
141 |
, (FieldDefinition "oper_state" "Running" QFTBool |
142 |
"Actual state of instance", |
143 |
FieldRuntime operStatusExtract, QffNormal) |
144 |
] ++ |
145 |
|
146 |
-- Simple live fields |
147 |
map instanceLiveFieldBuilder instanceLiveFieldsDefs ++ |
148 |
|
149 |
-- Generated fields |
150 |
serialFields "Instance" ++ |
151 |
uuidFields "Instance" ++ |
152 |
tagsFields |
153 |
|
154 |
-- * Helper functions for node property retrieval |
155 |
|
156 |
-- | Helper function for primary node retrieval |
157 |
getPrimaryNode :: ConfigData -> Instance -> ErrorResult Node |
158 |
getPrimaryNode cfg = getInstPrimaryNode cfg . instName |
159 |
|
160 |
-- | Get primary node hostname |
161 |
getPrimaryNodeName :: ConfigData -> Instance -> ResultEntry |
162 |
getPrimaryNodeName cfg inst = |
163 |
rsErrorNoData $ nodeName <$> getPrimaryNode cfg inst |
164 |
|
165 |
-- | Get primary node hostname |
166 |
getPrimaryNodeGroup :: ConfigData -> Instance -> ResultEntry |
167 |
getPrimaryNodeGroup cfg inst = |
168 |
rsErrorNoData $ (J.showJSON . groupName) <$> |
169 |
(getPrimaryNode cfg inst >>= |
170 |
maybeToError "Configuration missing" . getGroupOfNode cfg) |
171 |
|
172 |
-- | Get secondary nodes - the configuration objects themselves |
173 |
getSecondaryNodes :: ConfigData -> Instance -> ErrorResult [Node] |
174 |
getSecondaryNodes cfg inst = do |
175 |
pNode <- getPrimaryNode cfg inst |
176 |
allNodes <- getInstAllNodes cfg $ instName inst |
177 |
return $ delete pNode allNodes |
178 |
|
179 |
-- | Get attributes of the secondary nodes |
180 |
getSecondaryNodeAttribute :: (J.JSON a) |
181 |
=> (Node -> a) |
182 |
-> ConfigData |
183 |
-> Instance |
184 |
-> ResultEntry |
185 |
getSecondaryNodeAttribute getter cfg inst = |
186 |
rsErrorNoData $ map (J.showJSON . getter) <$> getSecondaryNodes cfg inst |
187 |
|
188 |
-- | Get secondary node groups |
189 |
getSecondaryNodeGroups :: ConfigData -> Instance -> ErrorResult [NodeGroup] |
190 |
getSecondaryNodeGroups cfg inst = do |
191 |
sNodes <- getSecondaryNodes cfg inst |
192 |
return . catMaybes $ map (getGroupOfNode cfg) sNodes |
193 |
|
194 |
-- | Get attributes of secondary node groups |
195 |
getSecondaryNodeGroupAttribute :: (J.JSON a) |
196 |
=> (NodeGroup -> a) |
197 |
-> ConfigData |
198 |
-> Instance |
199 |
-> ResultEntry |
200 |
getSecondaryNodeGroupAttribute getter cfg inst = |
201 |
rsErrorNoData $ map (J.showJSON . getter) <$> getSecondaryNodeGroups cfg inst |
202 |
|
203 |
-- | Beparam getter builder: given a field, it returns a FieldConfig |
204 |
-- getter, that is a function that takes the config and the object and |
205 |
-- returns the Beparam field specified when the getter was built. |
206 |
beParamGetter :: String -- ^ The field we are building the getter for |
207 |
-> ConfigData -- ^ The configuration object |
208 |
-> Instance -- ^ The instance configuration object |
209 |
-> ResultEntry -- ^ The result |
210 |
beParamGetter field config inst = |
211 |
case getFilledInstBeParams config inst of |
212 |
Ok beParams -> dictFieldGetter field $ Just beParams |
213 |
Bad _ -> rsNoData |
214 |
|
215 |
-- | Hvparam getter builder: given a field, it returns a FieldConfig |
216 |
-- getter, that is a function that takes the config and the object and |
217 |
-- returns the Hvparam field specified when the getter was built. |
218 |
hvParamGetter :: String -- ^ The field we're building the getter for |
219 |
-> ConfigData -> Instance -> ResultEntry |
220 |
hvParamGetter field cfg inst = |
221 |
rsMaybeUnavail . Map.lookup field . fromContainer $ |
222 |
getFilledInstHvParams cfg inst |
223 |
|
224 |
-- * Live fields functionality |
225 |
|
226 |
-- | List of node live fields. |
227 |
instanceLiveFieldsDefs :: [(FieldName, FieldTitle, FieldType, String, FieldDoc)] |
228 |
instanceLiveFieldsDefs = |
229 |
[ ("oper_ram", "Memory", QFTUnit, "oper_ram", |
230 |
"Actual memory usage as seen by hypervisor") |
231 |
, ("oper_vcpus", "VCPUs", QFTNumber, "oper_vcpus", |
232 |
"Actual number of VCPUs as seen by hypervisor") |
233 |
] |
234 |
|
235 |
-- | Map each name to a function that extracts that value from the RPC result. |
236 |
instanceLiveFieldExtract :: FieldName -> InstanceInfo -> Instance -> J.JSValue |
237 |
instanceLiveFieldExtract "oper_ram" info _ = J.showJSON $ instInfoMemory info |
238 |
instanceLiveFieldExtract "oper_vcpus" info _ = J.showJSON $ instInfoVcpus info |
239 |
instanceLiveFieldExtract n _ _ = J.showJSON $ |
240 |
"The field " ++ n ++ " is not an expected or extractable live field!" |
241 |
|
242 |
-- | Helper for extracting field from RPC result. |
243 |
instanceLiveRpcCall :: FieldName -> Runtime -> Instance -> ResultEntry |
244 |
instanceLiveRpcCall fname (Right (Just (res, _))) inst = |
245 |
case instanceLiveFieldExtract fname res inst of |
246 |
J.JSNull -> rsNoData |
247 |
x -> rsNormal x |
248 |
instanceLiveRpcCall _ (Right Nothing) _ = rsUnavail |
249 |
instanceLiveRpcCall _ (Left err) _ = |
250 |
ResultEntry (rpcErrorToStatus err) Nothing |
251 |
|
252 |
-- | Builder for node live fields. |
253 |
instanceLiveFieldBuilder :: (FieldName, FieldTitle, FieldType, String, FieldDoc) |
254 |
-> FieldData Instance Runtime |
255 |
instanceLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) = |
256 |
( FieldDefinition fname ftitle ftype fdoc |
257 |
, FieldRuntime $ instanceLiveRpcCall fname |
258 |
, QffNormal) |
259 |
|
260 |
-- * Functionality related to status and operational status extraction |
261 |
|
262 |
-- | The documentation text for the instance status field |
263 |
statusDocText :: String |
264 |
statusDocText = |
265 |
let si = show . instanceStatusToRaw :: InstanceStatus -> String |
266 |
in "Instance status; " ++ |
267 |
si Running ++ |
268 |
" if instance is set to be running and actually is, " ++ |
269 |
si StatusDown ++ |
270 |
" if instance is stopped and is not running, " ++ |
271 |
si WrongNode ++ |
272 |
" if instance running, but not on its designated primary node, " ++ |
273 |
si ErrorUp ++ |
274 |
" if instance should be stopped, but is actually running, " ++ |
275 |
si ErrorDown ++ |
276 |
" if instance should run, but doesn't, " ++ |
277 |
si NodeDown ++ |
278 |
" if instance's primary node is down, " ++ |
279 |
si NodeOffline ++ |
280 |
" if instance's primary node is marked offline, " ++ |
281 |
si StatusOffline ++ |
282 |
" if instance is offline and does not use dynamic resources" |
283 |
|
284 |
-- | Checks if the primary node of an instance is offline |
285 |
isPrimaryOffline :: ConfigData -> Instance -> Bool |
286 |
isPrimaryOffline cfg inst = |
287 |
let pNodeResult = getNode cfg $ instPrimaryNode inst |
288 |
in case pNodeResult of |
289 |
Ok pNode -> nodeOffline pNode |
290 |
Bad _ -> error "Programmer error - result assumed to be OK is Bad!" |
291 |
|
292 |
-- | Determines the status of a live instance |
293 |
liveInstanceStatus :: LiveInfo -> Instance -> InstanceStatus |
294 |
liveInstanceStatus (_, foundOnPrimary) inst |
295 |
| not foundOnPrimary = WrongNode |
296 |
| adminState == AdminUp = Running |
297 |
| otherwise = ErrorUp |
298 |
where adminState = instAdminState inst |
299 |
|
300 |
-- | Determines the status of a dead instance. |
301 |
deadInstanceStatus :: Instance -> InstanceStatus |
302 |
deadInstanceStatus inst = |
303 |
case instAdminState inst of |
304 |
AdminUp -> ErrorDown |
305 |
AdminDown -> StatusDown |
306 |
AdminOffline -> StatusOffline |
307 |
|
308 |
-- | Determines the status of the instance, depending on whether it is possible |
309 |
-- to communicate with its primary node, on which node it is, and its |
310 |
-- configuration. |
311 |
determineInstanceStatus :: ConfigData -- ^ The configuration data |
312 |
-> Runtime -- ^ All the data from the live call |
313 |
-> Instance -- ^ Static instance configuration |
314 |
-> InstanceStatus -- ^ Result |
315 |
determineInstanceStatus cfg res inst |
316 |
| isPrimaryOffline cfg inst = NodeOffline |
317 |
| otherwise = case res of |
318 |
Left _ -> NodeDown |
319 |
Right (Just liveData) -> liveInstanceStatus liveData inst |
320 |
Right Nothing -> deadInstanceStatus inst |
321 |
|
322 |
-- | Extracts the instance status, retrieving it using the functions above and |
323 |
-- transforming it into a 'ResultEntry'. |
324 |
statusExtract :: ConfigData -> Runtime -> Instance -> ResultEntry |
325 |
statusExtract cfg res inst = |
326 |
rsNormal . J.showJSON . instanceStatusToRaw $ |
327 |
determineInstanceStatus cfg res inst |
328 |
|
329 |
-- | Extracts the operational status of the instance. |
330 |
operStatusExtract :: Runtime -> Instance -> ResultEntry |
331 |
operStatusExtract res _ = |
332 |
rsMaybeNoData $ J.showJSON <$> |
333 |
case res of |
334 |
Left _ -> Nothing |
335 |
Right x -> Just $ isJust x |
336 |
|
337 |
-- * Helper functions extracting information as necessary for the generic query |
338 |
-- interfaces |
339 |
|
340 |
-- | Finds information about the instance in the info delivered by a node |
341 |
findInstanceInfo :: Instance |
342 |
-> ERpcError RpcResultAllInstancesInfo |
343 |
-> Maybe InstanceInfo |
344 |
findInstanceInfo inst nodeResponse = |
345 |
case nodeResponse of |
346 |
Left _err -> Nothing |
347 |
Right allInfo -> |
348 |
let instances = rpcResAllInstInfoInstances allInfo |
349 |
maybeMatch = pickPairUnique (instName inst) instances |
350 |
in snd <$> maybeMatch |
351 |
|
352 |
-- | Finds the node information ('RPCResultError') or the instance information |
353 |
-- (Maybe 'LiveInfo'). |
354 |
extractLiveInfo :: [(Node, ERpcError RpcResultAllInstancesInfo)] |
355 |
-> Instance |
356 |
-> Runtime |
357 |
extractLiveInfo nodeResultList inst = |
358 |
let uuidResultList = [(nodeUuid x, y) | (x, y) <- nodeResultList] |
359 |
pNodeUuid = instPrimaryNode inst |
360 |
maybeRPCError = getNodeStatus uuidResultList pNodeUuid |
361 |
in case maybeRPCError of |
362 |
Just err -> Left err |
363 |
Nothing -> Right $ getInstanceStatus uuidResultList pNodeUuid inst |
364 |
|
365 |
-- | Tries to find out if the node given by the uuid is bad - unreachable or |
366 |
-- returning errors, does not mather for the purpose of this call. |
367 |
getNodeStatus :: [(String, ERpcError RpcResultAllInstancesInfo)] |
368 |
-> String |
369 |
-> Maybe RpcError |
370 |
getNodeStatus uuidList uuid = |
371 |
case snd <$> pickPairUnique uuid uuidList of |
372 |
Just (Left err) -> Just err |
373 |
Just (Right _) -> Nothing |
374 |
Nothing -> Just . RpcResultError $ |
375 |
"Primary node response not present" |
376 |
|
377 |
-- | Retrieves the instance information if it is present anywhere in the all |
378 |
-- instances RPC result. Notes if it originates from the primary node. |
379 |
-- All nodes are represented as UUID's for ease of use. |
380 |
getInstanceStatus :: [(String, ERpcError RpcResultAllInstancesInfo)] |
381 |
-> String |
382 |
-> Instance |
383 |
-> Maybe LiveInfo |
384 |
getInstanceStatus uuidList pNodeUuid inst = |
385 |
let primarySearchResult = |
386 |
snd <$> pickPairUnique pNodeUuid uuidList >>= findInstanceInfo inst |
387 |
in case primarySearchResult of |
388 |
Just instInfo -> Just (instInfo, True) |
389 |
Nothing -> |
390 |
let allSearchResult = |
391 |
getFirst . mconcat $ map |
392 |
(First . findInstanceInfo inst . snd) uuidList |
393 |
in case allSearchResult of |
394 |
Just liveInfo -> Just (liveInfo, False) |
395 |
Nothing -> Nothing |
396 |
|
397 |
-- | Collect live data from RPC query if enabled. |
398 |
collectLiveData :: Bool -> ConfigData -> [Instance] -> IO [(Instance, Runtime)] |
399 |
collectLiveData liveDataEnabled cfg instances |
400 |
| not liveDataEnabled = return . zip instances . repeat . Left . |
401 |
RpcResultError $ "Live data disabled" |
402 |
| otherwise = do |
403 |
let hvSpec = getDefaultHypervisorSpec cfg |
404 |
instance_nodes = nub . justOk $ |
405 |
map (getNode cfg . instPrimaryNode) instances |
406 |
good_nodes = nodesWithValidConfig cfg instance_nodes |
407 |
rpcres <- executeRpcCall good_nodes $ RpcCallAllInstancesInfo [hvSpec] |
408 |
return . zip instances . map (extractLiveInfo rpcres) $ instances |