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)
44 -- * Utility functions
46 -- | Ensure a given JSValue is actually a JSArray.
47 toArray :: (Monad m) => JSValue -> m [JSValue]
50 JSArray arr -> return arr
51 o -> fail ("Invalid input, expected array but got " ++ show o)
53 -- | Annotate errors when converting values with owner/attribute for
55 genericConvert :: (Text.JSON.JSON a) =>
56 String -- ^ The object type
57 -> String -- ^ The object name
58 -> String -- ^ The attribute we're trying to convert
59 -> JSValue -- ^ The value we try to convert
60 -> Result a -- ^ The annotated result
61 genericConvert otype oname oattr =
62 annotateResult (otype ++ " '" ++ oname ++ "', attribute '" ++
63 oattr ++ "'") . fromJVal
65 -- * Data querying functionality
67 -- | The input data for node query.
68 queryNodesMsg :: L.LuxiOp
70 L.QueryNodes [] ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
71 "ctotal", "offline", "drained", "vm_capable",
74 -- | The input data for instance query.
75 queryInstancesMsg :: L.LuxiOp
77 L.QueryInstances [] ["name", "disk_usage", "be/memory", "be/vcpus",
78 "status", "pnode", "snodes", "tags", "oper_ram"] False
80 -- | The input data for cluster query.
81 queryClusterInfoMsg :: L.LuxiOp
82 queryClusterInfoMsg = L.QueryClusterInfo
84 -- | The input data for node group query.
85 queryGroupsMsg :: L.LuxiOp
87 L.QueryGroups [] ["uuid", "name", "alloc_policy"] False
89 -- | Wraper over callMethod doing node query.
90 queryNodes :: L.Client -> IO (Result JSValue)
91 queryNodes = L.callMethod queryNodesMsg
93 -- | Wraper over callMethod doing instance query.
94 queryInstances :: L.Client -> IO (Result JSValue)
95 queryInstances = L.callMethod queryInstancesMsg
97 queryClusterInfo :: L.Client -> IO (Result JSValue)
98 queryClusterInfo = L.callMethod queryClusterInfoMsg
100 -- | Wrapper over callMethod doing group query.
101 queryGroups :: L.Client -> IO (Result JSValue)
102 queryGroups = L.callMethod queryGroupsMsg
104 -- | Parse a instance list in JSON format.
105 getInstances :: NameAssoc
107 -> Result [(String, Instance.Instance)]
108 getInstances ktn arr = toArray arr >>= mapM (parseInstance ktn)
110 -- | Construct an instance from a JSON object.
111 parseInstance :: NameAssoc
113 -> Result (String, Instance.Instance)
114 parseInstance ktn (JSArray [ name, disk, mem, vcpus
115 , status, pnode, snodes, tags, oram ]) = do
116 xname <- annotateResult "Parsing new instance" (fromJVal name)
117 let convert a = genericConvert "Instance" xname a
118 xdisk <- convert "disk_usage" disk
119 xmem <- (case oram of
120 JSRational _ _ -> convert "oper_ram" oram
121 _ -> convert "be/memory" mem)
122 xvcpus <- convert "be/vcpus" vcpus
123 xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
124 xsnodes <- convert "snodes" snodes::Result [JSString]
125 snode <- (if null xsnodes then return Node.noSecondary
126 else lookupNode ktn xname (fromJSString $ head xsnodes))
127 xrunning <- convert "status" status
128 xtags <- convert "tags" tags
129 let inst = Instance.create xname xmem xdisk xvcpus
130 xrunning xtags xpnode snode
133 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
135 -- | Parse a node list in JSON format.
136 getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
137 getNodes ktg arr = toArray arr >>= mapM (parseNode ktg)
139 -- | Construct a node from a JSON object.
140 parseNode :: NameAssoc -> JSValue -> Result (String, Node.Node)
141 parseNode ktg (JSArray [ name, mtotal, mnode, mfree, dtotal, dfree
142 , ctotal, offline, drained, vm_capable, g_uuid ])
144 xname <- annotateResult "Parsing new node" (fromJVal name)
145 let convert a = genericConvert "Node" xname a
146 xoffline <- convert "offline" offline
147 xdrained <- convert "drained" drained
148 xvm_capable <- convert "vm_capable" vm_capable
149 xgdx <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
150 node <- (if xoffline || xdrained || not xvm_capable
151 then return $ Node.create xname 0 0 0 0 0 0 True xgdx
153 xmtotal <- convert "mtotal" mtotal
154 xmnode <- convert "mnode" mnode
155 xmfree <- convert "mfree" mfree
156 xdtotal <- convert "dtotal" dtotal
157 xdfree <- convert "dfree" dfree
158 xctotal <- convert "ctotal" ctotal
159 return $ Node.create xname xmtotal xmnode xmfree
160 xdtotal xdfree xctotal False xgdx)
163 parseNode _ v = fail ("Invalid node query result: " ++ show v)
165 getClusterTags :: JSValue -> Result [String]
166 getClusterTags v = do
167 let errmsg = "Parsing cluster info"
168 obj <- annotateResult errmsg $ asJSObject v
169 tryFromObj errmsg (fromJSObject obj) "tags"
171 getGroups :: JSValue -> Result [(String, Group.Group)]
172 getGroups arr = toArray arr >>= mapM parseGroup
174 parseGroup :: JSValue -> Result (String, Group.Group)
175 parseGroup (JSArray [ uuid, name, apol ]) = do
176 xname <- annotateResult "Parsing new group" (fromJVal name)
177 let convert a = genericConvert "Group" xname a
178 xuuid <- convert "uuid" uuid
179 xapol <- convert "alloc_policy" apol
180 return (xuuid, Group.create xname xuuid xapol)
182 parseGroup v = fail ("Invalid group query result: " ++ show v)
184 -- * Main loader functionality
186 -- | Builds the cluster data from an URL.
187 readData :: String -- ^ Unix socket to use as source
188 -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
194 nodes <- queryNodes s
195 instances <- queryInstances s
196 cinfo <- queryClusterInfo s
197 groups <- queryGroups s
198 return (groups, nodes, instances, cinfo)
201 parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
202 -> Result ClusterData
203 parseData (groups, nodes, instances, cinfo) = do
204 group_data <- groups >>= getGroups
205 let (group_names, group_idx) = assignIndices group_data
206 node_data <- nodes >>= getNodes group_names
207 let (node_names, node_idx) = assignIndices node_data
208 inst_data <- instances >>= getInstances node_names
209 let (_, inst_idx) = assignIndices inst_data
210 ctags <- cinfo >>= getClusterTags
211 return (ClusterData group_idx node_idx inst_idx ctags)
213 -- | Top level function for data loading
214 loadData :: String -- ^ Unix socket to use as source
215 -> IO (Result ClusterData)
216 loadData = fmap parseData . readData