1 {-| Implementation of the LUXI loader.
7 Copyright (C) 2009, 2010 Google Inc.
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.
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.
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
26 module Ganeti.HTools.Luxi
31 import qualified Control.Exception as E
32 import Text.JSON.Types
34 import qualified Ganeti.Luxi as L
35 import Ganeti.HTools.Loader
36 import Ganeti.HTools.Types
37 import qualified Ganeti.HTools.Node as Node
38 import qualified Ganeti.HTools.Instance as Instance
39 import Ganeti.HTools.Utils (fromJVal, annotateResult, tryFromObj, asJSObject)
41 -- * Utility functions
43 -- | Ensure a given JSValue is actually a JSArray.
44 toArray :: (Monad m) => JSValue -> m [JSValue]
47 JSArray arr -> return arr
48 o -> fail ("Invalid input, expected array but got " ++ show o)
50 -- * Data querying functionality
52 -- | The input data for node query.
53 queryNodesMsg :: L.LuxiOp
55 L.QueryNodes [] ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
56 "ctotal", "offline", "drained", "vm_capable"] False
58 -- | The input data for instance query.
59 queryInstancesMsg :: L.LuxiOp
61 L.QueryInstances [] ["name", "disk_usage", "be/memory", "be/vcpus",
62 "status", "pnode", "snodes", "tags", "oper_ram"] False
64 -- | The input data for cluster query
65 queryClusterInfoMsg :: L.LuxiOp
66 queryClusterInfoMsg = L.QueryClusterInfo
68 -- | Wraper over callMethod doing node query.
69 queryNodes :: L.Client -> IO (Result JSValue)
70 queryNodes = L.callMethod queryNodesMsg
72 -- | Wraper over callMethod doing instance query.
73 queryInstances :: L.Client -> IO (Result JSValue)
74 queryInstances = L.callMethod queryInstancesMsg
76 queryClusterInfo :: L.Client -> IO (Result JSValue)
77 queryClusterInfo = L.callMethod queryClusterInfoMsg
79 -- | Parse a instance list in JSON format.
80 getInstances :: NameAssoc
82 -> Result [(String, Instance.Instance)]
83 getInstances ktn arr = toArray arr >>= mapM (parseInstance ktn)
85 -- | Construct an instance from a JSON object.
86 parseInstance :: [(String, Ndx)]
88 -> Result (String, Instance.Instance)
89 parseInstance ktn (JSArray [ name, disk, mem, vcpus
90 , status, pnode, snodes, tags, oram ]) = do
91 xname <- annotateResult "Parsing new instance" (fromJVal name)
92 let convert v = annotateResult ("Instance '" ++ xname ++ "'") (fromJVal v)
95 JSRational _ _ -> convert oram
97 xvcpus <- convert vcpus
98 xpnode <- convert pnode >>= lookupNode ktn xname
99 xsnodes <- convert snodes::Result [JSString]
100 snode <- (if null xsnodes then return Node.noSecondary
101 else lookupNode ktn xname (fromJSString $ head xsnodes))
102 xrunning <- convert status
103 xtags <- convert tags
104 let inst = Instance.create xname xmem xdisk xvcpus
105 xrunning xtags xpnode snode
108 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
110 -- | Parse a node list in JSON format.
111 getNodes :: JSValue -> Result [(String, Node.Node)]
112 getNodes arr = toArray arr >>= mapM parseNode
114 -- | Construct a node from a JSON object.
115 parseNode :: JSValue -> Result (String, Node.Node)
116 parseNode (JSArray [ name, mtotal, mnode, mfree, dtotal, dfree
117 , ctotal, offline, drained, vm_capable ])
119 xname <- annotateResult "Parsing new node" (fromJVal name)
120 let convert v = annotateResult ("Node '" ++ xname ++ "'") (fromJVal v)
121 xoffline <- convert offline
122 xdrained <- convert drained
123 xvm_capable <- convert vm_capable
124 node <- (if xoffline || xdrained || not xvm_capable
125 then return $ Node.create xname 0 0 0 0 0 0 True
127 xmtotal <- convert mtotal
128 xmnode <- convert mnode
129 xmfree <- convert mfree
130 xdtotal <- convert dtotal
131 xdfree <- convert dfree
132 xctotal <- convert ctotal
133 return $ Node.create xname xmtotal xmnode xmfree
134 xdtotal xdfree xctotal False)
137 parseNode v = fail ("Invalid node query result: " ++ show v)
139 getClusterTags :: JSValue -> Result [String]
140 getClusterTags v = do
141 let errmsg = "Parsing cluster info"
142 obj <- annotateResult errmsg $ asJSObject v
143 tryFromObj errmsg (fromJSObject obj) "tags"
145 -- * Main loader functionality
147 -- | Builds the cluster data from an URL.
148 loadData :: String -- ^ Unix socket to use as source
149 -> IO (Result (Node.AssocList, Instance.AssocList, [String]))
155 nodes <- queryNodes s
156 instances <- queryInstances s
157 cinfo <- queryClusterInfo s
158 return $ do -- Result monad
159 node_data <- nodes >>= getNodes
160 let (node_names, node_idx) = assignIndices node_data
161 inst_data <- instances >>= getInstances node_names
162 let (_, inst_idx) = assignIndices inst_data
163 ctags <- cinfo >>= getClusterTags
164 return (node_idx, inst_idx, ctags)