1 {-| Implementation of the LUXI loader.
7 Copyright (C) 2009, 2010, 2011 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
32 import qualified Control.Exception as E
33 import Text.JSON.Types
34 import qualified Text.JSON
36 import qualified Ganeti.Luxi as L
37 import Ganeti.HTools.Loader
38 import Ganeti.HTools.Types
39 import qualified Ganeti.HTools.Group as Group
40 import qualified Ganeti.HTools.Node as Node
41 import qualified Ganeti.HTools.Instance as Instance
42 import Ganeti.HTools.Utils (fromJVal, annotateResult, tryFromObj, asJSObject,
45 -- * Utility functions
47 -- | Get values behind \"data\" part of the result.
48 getData :: (Monad m) => JSValue -> m JSValue
49 getData (JSObject o) = fromObj (fromJSObject o) "data"
50 getData x = fail $ "Invalid input, expected dict entry but got " ++ show x
52 -- | Converts a (status, value) into m value, if possible.
53 parseQueryField :: (Monad m) => JSValue -> m (JSValue, JSValue)
54 parseQueryField (JSArray [status, result]) = return (status, result)
56 fail $ "Invalid query field, expected (status, value) but got " ++ show o
58 -- | Parse a result row.
59 parseQueryRow :: (Monad m) => JSValue -> m [(JSValue, JSValue)]
60 parseQueryRow (JSArray arr) = mapM parseQueryField arr
62 fail $ "Invalid query row result, expected array but got " ++ show o
64 -- | Parse an overall query result and get the [(status, value)] list
65 -- for each element queried.
66 parseQueryResult :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]]
67 parseQueryResult (JSArray arr) = mapM parseQueryRow arr
69 fail $ "Invalid query result, expected array but got " ++ show o
71 -- | Prepare resulting output as parsers expect it.
72 extractArray :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]]
74 getData v >>= parseQueryResult
76 -- | Testing result status for more verbose error message.
77 fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a
78 fromJValWithStatus (st, v) = do
80 L.checkRS st' v >>= fromJVal
82 -- | Annotate errors when converting values with owner/attribute for
84 genericConvert :: (Text.JSON.JSON a) =>
85 String -- ^ The object type
86 -> String -- ^ The object name
87 -> String -- ^ The attribute we're trying to convert
88 -> (JSValue, JSValue) -- ^ The value we're trying to convert
89 -> Result a -- ^ The annotated result
90 genericConvert otype oname oattr =
91 annotateResult (otype ++ " '" ++ oname ++
92 "', error while reading attribute '" ++
93 oattr ++ "'") . fromJValWithStatus
95 -- * Data querying functionality
97 -- | The input data for node query.
98 queryNodesMsg :: L.LuxiOp
100 L.Query L.QRNode ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
101 "ctotal", "offline", "drained", "vm_capable",
104 -- | The input data for instance query.
105 queryInstancesMsg :: L.LuxiOp
107 L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
108 "status", "pnode", "snodes", "tags", "oper_ram",
109 "be/auto_balance", "disk_template"] ()
111 -- | The input data for cluster query.
112 queryClusterInfoMsg :: L.LuxiOp
113 queryClusterInfoMsg = L.QueryClusterInfo
115 -- | The input data for node group query.
116 queryGroupsMsg :: L.LuxiOp
118 L.Query L.QRGroup ["uuid", "name", "alloc_policy"] ()
120 -- | Wraper over 'callMethod' doing node query.
121 queryNodes :: L.Client -> IO (Result JSValue)
122 queryNodes = L.callMethod queryNodesMsg
124 -- | Wraper over 'callMethod' doing instance query.
125 queryInstances :: L.Client -> IO (Result JSValue)
126 queryInstances = L.callMethod queryInstancesMsg
128 -- | Wrapper over 'callMethod' doing cluster information query.
129 queryClusterInfo :: L.Client -> IO (Result JSValue)
130 queryClusterInfo = L.callMethod queryClusterInfoMsg
132 -- | Wrapper over callMethod doing group query.
133 queryGroups :: L.Client -> IO (Result JSValue)
134 queryGroups = L.callMethod queryGroupsMsg
136 -- | Parse a instance list in JSON format.
137 getInstances :: NameAssoc
139 -> Result [(String, Instance.Instance)]
140 getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
142 -- | Construct an instance from a JSON object.
143 parseInstance :: NameAssoc
144 -> [(JSValue, JSValue)]
145 -> Result (String, Instance.Instance)
146 parseInstance ktn [ name, disk, mem, vcpus
147 , status, pnode, snodes, tags, oram
148 , auto_balance, disk_template ] = do
149 xname <- annotateResult "Parsing new instance" (fromJValWithStatus name)
150 let convert a = genericConvert "Instance" xname a
151 xdisk <- convert "disk_usage" disk
152 xmem <- (case oram of -- FIXME: remove the "guessing"
153 (_, JSRational _ _) -> convert "oper_ram" oram
154 _ -> convert "be/memory" mem)
155 xvcpus <- convert "be/vcpus" vcpus
156 xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
157 xsnodes <- convert "snodes" snodes::Result [JSString]
158 snode <- (if null xsnodes then return Node.noSecondary
159 else lookupNode ktn xname (fromJSString $ head xsnodes))
160 xrunning <- convert "status" status
161 xtags <- convert "tags" tags
162 xauto_balance <- convert "auto_balance" auto_balance
163 xdt <- convert "disk_template" disk_template
164 let inst = Instance.create xname xmem xdisk xvcpus
165 xrunning xtags xauto_balance xpnode snode xdt
168 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
170 -- | Parse a node list in JSON format.
171 getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
172 getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
174 -- | Construct a node from a JSON object.
175 parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node)
176 parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree
177 , ctotal, offline, drained, vm_capable, g_uuid ]
179 xname <- annotateResult "Parsing new node" (fromJValWithStatus name)
180 let convert a = genericConvert "Node" xname a
181 xoffline <- convert "offline" offline
182 xdrained <- convert "drained" drained
183 xvm_capable <- convert "vm_capable" vm_capable
184 xgdx <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
185 node <- (if xoffline || xdrained || not xvm_capable
186 then return $ Node.create xname 0 0 0 0 0 0 True xgdx
188 xmtotal <- convert "mtotal" mtotal
189 xmnode <- convert "mnode" mnode
190 xmfree <- convert "mfree" mfree
191 xdtotal <- convert "dtotal" dtotal
192 xdfree <- convert "dfree" dfree
193 xctotal <- convert "ctotal" ctotal
194 return $ Node.create xname xmtotal xmnode xmfree
195 xdtotal xdfree xctotal False xgdx)
198 parseNode _ v = fail ("Invalid node query result: " ++ show v)
200 -- | Parses the cluster tags.
201 getClusterTags :: JSValue -> Result [String]
202 getClusterTags v = do
203 let errmsg = "Parsing cluster info"
204 obj <- annotateResult errmsg $ asJSObject v
205 tryFromObj errmsg (fromJSObject obj) "tags"
207 -- | Parses the cluster groups.
208 getGroups :: JSValue -> Result [(String, Group.Group)]
209 getGroups jsv = extractArray jsv >>= mapM parseGroup
211 -- | Parses a given group information.
212 parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group)
213 parseGroup [uuid, name, apol] = do
214 xname <- annotateResult "Parsing new group" (fromJValWithStatus name)
215 let convert a = genericConvert "Group" xname a
216 xuuid <- convert "uuid" uuid
217 xapol <- convert "alloc_policy" apol
218 return (xuuid, Group.create xname xuuid xapol)
220 parseGroup v = fail ("Invalid group query result: " ++ show v)
222 -- * Main loader functionality
224 -- | Builds the cluster data by querying a given socket name.
225 readData :: String -- ^ Unix socket to use as source
226 -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
232 nodes <- queryNodes s
233 instances <- queryInstances s
234 cinfo <- queryClusterInfo s
235 groups <- queryGroups s
236 return (groups, nodes, instances, cinfo)
239 -- | Converts the output of 'readData' into the internal cluster
241 parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
242 -> Result ClusterData
243 parseData (groups, nodes, instances, cinfo) = do
244 group_data <- groups >>= getGroups
245 let (group_names, group_idx) = assignIndices group_data
246 node_data <- nodes >>= getNodes group_names
247 let (node_names, node_idx) = assignIndices node_data
248 inst_data <- instances >>= getInstances node_names
249 let (_, inst_idx) = assignIndices inst_data
250 ctags <- cinfo >>= getClusterTags
251 return (ClusterData group_idx node_idx inst_idx ctags)
253 -- | Top level function for data loading.
254 loadData :: String -- ^ Unix socket to use as source
255 -> IO (Result ClusterData)
256 loadData = fmap parseData . readData