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]
74 getData v >>= parseQueryResult >>= (return . map (JSArray . map snd))
76 -- | Annotate errors when converting values with owner/attribute for
78 genericConvert :: (Text.JSON.JSON a) =>
79 String -- ^ The object type
80 -> String -- ^ The object name
81 -> String -- ^ The attribute we're trying to convert
82 -> JSValue -- ^ The value we try to convert
83 -> Result a -- ^ The annotated result
84 genericConvert otype oname oattr =
85 annotateResult (otype ++ " '" ++ oname ++
86 "', error while reading attribute '" ++
87 oattr ++ "'") . fromJVal
89 -- * Data querying functionality
91 -- | The input data for node query.
92 queryNodesMsg :: L.LuxiOp
94 L.Query L.QRNode ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
95 "ctotal", "offline", "drained", "vm_capable",
98 -- | The input data for instance query.
99 queryInstancesMsg :: L.LuxiOp
101 L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
102 "status", "pnode", "snodes", "tags", "oper_ram",
103 "be/auto_balance", "disk_template"] ()
105 -- | The input data for cluster query.
106 queryClusterInfoMsg :: L.LuxiOp
107 queryClusterInfoMsg = L.QueryClusterInfo
109 -- | The input data for node group query.
110 queryGroupsMsg :: L.LuxiOp
112 L.Query L.QRGroup ["uuid", "name", "alloc_policy"] ()
114 -- | Wraper over 'callMethod' doing node query.
115 queryNodes :: L.Client -> IO (Result JSValue)
116 queryNodes = L.callMethod queryNodesMsg
118 -- | Wraper over 'callMethod' doing instance query.
119 queryInstances :: L.Client -> IO (Result JSValue)
120 queryInstances = L.callMethod queryInstancesMsg
122 -- | Wrapper over 'callMethod' doing cluster information query.
123 queryClusterInfo :: L.Client -> IO (Result JSValue)
124 queryClusterInfo = L.callMethod queryClusterInfoMsg
126 -- | Wrapper over callMethod doing group query.
127 queryGroups :: L.Client -> IO (Result JSValue)
128 queryGroups = L.callMethod queryGroupsMsg
130 -- | Parse a instance list in JSON format.
131 getInstances :: NameAssoc
133 -> Result [(String, Instance.Instance)]
134 getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
136 -- | Construct an instance from a JSON object.
137 parseInstance :: NameAssoc
139 -> Result (String, Instance.Instance)
140 parseInstance ktn (JSArray [ name, disk, mem, vcpus
141 , status, pnode, snodes, tags, oram
142 , auto_balance, disk_template ]) = do
143 xname <- annotateResult "Parsing new instance" (fromJVal name)
144 let convert a = genericConvert "Instance" xname a
145 xdisk <- convert "disk_usage" disk
146 xmem <- (case oram of
147 JSRational _ _ -> convert "oper_ram" oram
148 _ -> convert "be/memory" mem)
149 xvcpus <- convert "be/vcpus" vcpus
150 xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
151 xsnodes <- convert "snodes" snodes::Result [JSString]
152 snode <- (if null xsnodes then return Node.noSecondary
153 else lookupNode ktn xname (fromJSString $ head xsnodes))
154 xrunning <- convert "status" status
155 xtags <- convert "tags" tags
156 xauto_balance <- convert "auto_balance" auto_balance
157 xdt <- convert "disk_template" disk_template
158 let inst = Instance.create xname xmem xdisk xvcpus
159 xrunning xtags xauto_balance xpnode snode xdt
162 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
164 -- | Parse a node list in JSON format.
165 getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
166 getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
168 -- | Construct a node from a JSON object.
169 parseNode :: NameAssoc -> JSValue -> Result (String, Node.Node)
170 parseNode ktg (JSArray [ name, mtotal, mnode, mfree, dtotal, dfree
171 , ctotal, offline, drained, vm_capable, g_uuid ])
173 xname <- annotateResult "Parsing new node" (fromJVal name)
174 let convert a = genericConvert "Node" xname a
175 xoffline <- convert "offline" offline
176 xdrained <- convert "drained" drained
177 xvm_capable <- convert "vm_capable" vm_capable
178 xgdx <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
179 node <- (if xoffline || xdrained || not xvm_capable
180 then return $ Node.create xname 0 0 0 0 0 0 True xgdx
182 xmtotal <- convert "mtotal" mtotal
183 xmnode <- convert "mnode" mnode
184 xmfree <- convert "mfree" mfree
185 xdtotal <- convert "dtotal" dtotal
186 xdfree <- convert "dfree" dfree
187 xctotal <- convert "ctotal" ctotal
188 return $ Node.create xname xmtotal xmnode xmfree
189 xdtotal xdfree xctotal False xgdx)
192 parseNode _ v = fail ("Invalid node query result: " ++ show v)
194 -- | Parses the cluster tags.
195 getClusterTags :: JSValue -> Result [String]
196 getClusterTags v = do
197 let errmsg = "Parsing cluster info"
198 obj <- annotateResult errmsg $ asJSObject v
199 tryFromObj errmsg (fromJSObject obj) "tags"
201 -- | Parses the cluster groups.
202 getGroups :: JSValue -> Result [(String, Group.Group)]
203 getGroups jsv = extractArray jsv >>= mapM parseGroup
205 -- | Parses a given group information.
206 parseGroup :: JSValue -> Result (String, Group.Group)
207 parseGroup (JSArray [uuid, name, apol]) = do
208 xname <- annotateResult "Parsing new group" (fromJVal name)
209 let convert a = genericConvert "Group" xname a
210 xuuid <- convert "uuid" uuid
211 xapol <- convert "alloc_policy" apol
212 return (xuuid, Group.create xname xuuid xapol)
214 parseGroup v = fail ("Invalid group query result: " ++ show v)
216 -- * Main loader functionality
218 -- | Builds the cluster data by querying a given socket name.
219 readData :: String -- ^ Unix socket to use as source
220 -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
226 nodes <- queryNodes s
227 instances <- queryInstances s
228 cinfo <- queryClusterInfo s
229 groups <- queryGroups s
230 return (groups, nodes, instances, cinfo)
233 -- | Converts the output of 'readData' into the internal cluster
235 parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
236 -> Result ClusterData
237 parseData (groups, nodes, instances, cinfo) = do
238 group_data <- groups >>= getGroups
239 let (group_names, group_idx) = assignIndices group_data
240 node_data <- nodes >>= getNodes group_names
241 let (node_names, node_idx) = assignIndices node_data
242 inst_data <- instances >>= getInstances node_names
243 let (_, inst_idx) = assignIndices inst_data
244 ctags <- cinfo >>= getClusterTags
245 return (ClusterData group_idx node_idx inst_idx ctags)
247 -- | Top level function for data loading.
248 loadData :: String -- ^ Unix socket to use as source
249 -> IO (Result ClusterData)
250 loadData = fmap parseData . readData