Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ ebf38064

History | View | Annotate | Download (9.6 kB)

1
{-| Implementation of the LUXI loader.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2009, 2010, 2011 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.HTools.Luxi
27
  ( loadData
28
  , parseData
29
  ) where
30

    
31
import qualified Control.Exception as E
32
import Text.JSON.Types
33
import qualified Text.JSON
34

    
35
import qualified Ganeti.Luxi as L
36
import Ganeti.HTools.Loader
37
import Ganeti.HTools.Types
38
import qualified Ganeti.HTools.Group as Group
39
import qualified Ganeti.HTools.Node as Node
40
import qualified Ganeti.HTools.Instance as Instance
41
import Ganeti.HTools.Utils (fromJVal, annotateResult, tryFromObj, asJSObject,
42
                            fromObj)
43

    
44
-- * Utility functions
45

    
46
-- | Get values behind \"data\" part of the result.
47
getData :: (Monad m) => JSValue -> m JSValue
48
getData (JSObject o) = fromObj (fromJSObject o) "data"
49
getData x = fail $ "Invalid input, expected dict entry but got " ++ show x
50

    
51
-- | Converts a (status, value) into m value, if possible.
52
parseQueryField :: (Monad m) => JSValue -> m (JSValue, JSValue)
53
parseQueryField (JSArray [status, result]) = return (status, result)
54
parseQueryField o =
55
  fail $ "Invalid query field, expected (status, value) but got " ++ show o
56

    
57
-- | Parse a result row.
58
parseQueryRow :: (Monad m) => JSValue -> m [(JSValue, JSValue)]
59
parseQueryRow (JSArray arr) = mapM parseQueryField arr
60
parseQueryRow o =
61
  fail $ "Invalid query row result, expected array but got " ++ show o
62

    
63
-- | Parse an overall query result and get the [(status, value)] list
64
-- for each element queried.
65
parseQueryResult :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]]
66
parseQueryResult (JSArray arr) = mapM parseQueryRow arr
67
parseQueryResult o =
68
  fail $ "Invalid query result, expected array but got " ++ show o
69

    
70
-- | Prepare resulting output as parsers expect it.
71
extractArray :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]]
72
extractArray v =
73
  getData v >>= parseQueryResult
74

    
75
-- | Testing result status for more verbose error message.
76
fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a
77
fromJValWithStatus (st, v) = do
78
  st' <- fromJVal st
79
  L.checkRS st' v >>= fromJVal
80

    
81
-- | Annotate errors when converting values with owner/attribute for
82
-- better debugging.
83
genericConvert :: (Text.JSON.JSON a) =>
84
                  String             -- ^ The object type
85
               -> String             -- ^ The object name
86
               -> String             -- ^ The attribute we're trying to convert
87
               -> (JSValue, JSValue) -- ^ The value we're trying to convert
88
               -> Result a           -- ^ The annotated result
89
genericConvert otype oname oattr =
90
  annotateResult (otype ++ " '" ++ oname ++
91
                  "', error while reading attribute '" ++
92
                  oattr ++ "'") . fromJValWithStatus
93

    
94
-- * Data querying functionality
95

    
96
-- | The input data for node query.
97
queryNodesMsg :: L.LuxiOp
98
queryNodesMsg =
99
  L.Query L.QRNode ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
100
                    "ctotal", "offline", "drained", "vm_capable",
101
                    "group.uuid"] ()
102

    
103
-- | The input data for instance query.
104
queryInstancesMsg :: L.LuxiOp
105
queryInstancesMsg =
106
  L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
107
                        "status", "pnode", "snodes", "tags", "oper_ram",
108
                        "be/auto_balance", "disk_template"] ()
109

    
110
-- | The input data for cluster query.
111
queryClusterInfoMsg :: L.LuxiOp
112
queryClusterInfoMsg = L.QueryClusterInfo
113

    
114
-- | The input data for node group query.
115
queryGroupsMsg :: L.LuxiOp
116
queryGroupsMsg =
117
  L.Query L.QRGroup ["uuid", "name", "alloc_policy"] ()
118

    
119
-- | Wraper over 'callMethod' doing node query.
120
queryNodes :: L.Client -> IO (Result JSValue)
121
queryNodes = L.callMethod queryNodesMsg
122

    
123
-- | Wraper over 'callMethod' doing instance query.
124
queryInstances :: L.Client -> IO (Result JSValue)
125
queryInstances = L.callMethod queryInstancesMsg
126

    
127
-- | Wrapper over 'callMethod' doing cluster information query.
128
queryClusterInfo :: L.Client -> IO (Result JSValue)
129
queryClusterInfo = L.callMethod queryClusterInfoMsg
130

    
131
-- | Wrapper over callMethod doing group query.
132
queryGroups :: L.Client -> IO (Result JSValue)
133
queryGroups = L.callMethod queryGroupsMsg
134

    
135
-- | Parse a instance list in JSON format.
136
getInstances :: NameAssoc
137
             -> JSValue
138
             -> Result [(String, Instance.Instance)]
139
getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
140

    
141
-- | Construct an instance from a JSON object.
142
parseInstance :: NameAssoc
143
              -> [(JSValue, JSValue)]
144
              -> Result (String, Instance.Instance)
145
parseInstance ktn [ name, disk, mem, vcpus
146
                  , status, pnode, snodes, tags, oram
147
                  , auto_balance, disk_template ] = do
148
  xname <- annotateResult "Parsing new instance" (fromJValWithStatus name)
149
  let convert a = genericConvert "Instance" xname a
150
  xdisk <- convert "disk_usage" disk
151
  xmem <- (case oram of -- FIXME: remove the "guessing"
152
             (_, JSRational _ _) -> convert "oper_ram" oram
153
             _ -> convert "be/memory" mem)
154
  xvcpus <- convert "be/vcpus" vcpus
155
  xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
156
  xsnodes <- convert "snodes" snodes::Result [JSString]
157
  snode <- (if null xsnodes then return Node.noSecondary
158
            else lookupNode ktn xname (fromJSString $ head xsnodes))
159
  xrunning <- convert "status" status
160
  xtags <- convert "tags" tags
161
  xauto_balance <- convert "auto_balance" auto_balance
162
  xdt <- convert "disk_template" disk_template
163
  let inst = Instance.create xname xmem xdisk xvcpus
164
             xrunning xtags xauto_balance xpnode snode xdt
165
  return (xname, inst)
166

    
167
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
168

    
169
-- | Parse a node list in JSON format.
170
getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
171
getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
172

    
173
-- | Construct a node from a JSON object.
174
parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node)
175
parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree
176
              , ctotal, offline, drained, vm_capable, g_uuid ]
177
    = do
178
  xname <- annotateResult "Parsing new node" (fromJValWithStatus name)
179
  let convert a = genericConvert "Node" xname a
180
  xoffline <- convert "offline" offline
181
  xdrained <- convert "drained" drained
182
  xvm_capable <- convert "vm_capable" vm_capable
183
  xgdx   <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
184
  node <- (if xoffline || xdrained || not xvm_capable
185
           then return $ Node.create xname 0 0 0 0 0 0 True xgdx
186
           else do
187
             xmtotal  <- convert "mtotal" mtotal
188
             xmnode   <- convert "mnode" mnode
189
             xmfree   <- convert "mfree" mfree
190
             xdtotal  <- convert "dtotal" dtotal
191
             xdfree   <- convert "dfree" dfree
192
             xctotal  <- convert "ctotal" ctotal
193
             return $ Node.create xname xmtotal xmnode xmfree
194
                    xdtotal xdfree xctotal False xgdx)
195
  return (xname, node)
196

    
197
parseNode _ v = fail ("Invalid node query result: " ++ show v)
198

    
199
-- | Parses the cluster tags.
200
getClusterTags :: JSValue -> Result [String]
201
getClusterTags v = do
202
  let errmsg = "Parsing cluster info"
203
  obj <- annotateResult errmsg $ asJSObject v
204
  tryFromObj errmsg (fromJSObject obj) "tags"
205

    
206
-- | Parses the cluster groups.
207
getGroups :: JSValue -> Result [(String, Group.Group)]
208
getGroups jsv = extractArray jsv >>= mapM parseGroup
209

    
210
-- | Parses a given group information.
211
parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group)
212
parseGroup [uuid, name, apol] = do
213
  xname <- annotateResult "Parsing new group" (fromJValWithStatus name)
214
  let convert a = genericConvert "Group" xname a
215
  xuuid <- convert "uuid" uuid
216
  xapol <- convert "alloc_policy" apol
217
  return (xuuid, Group.create xname xuuid xapol)
218

    
219
parseGroup v = fail ("Invalid group query result: " ++ show v)
220

    
221
-- * Main loader functionality
222

    
223
-- | Builds the cluster data by querying a given socket name.
224
readData :: String -- ^ Unix socket to use as source
225
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
226
readData master =
227
  E.bracket
228
       (L.getClient master)
229
       L.closeClient
230
       (\s -> do
231
          nodes <- queryNodes s
232
          instances <- queryInstances s
233
          cinfo <- queryClusterInfo s
234
          groups <- queryGroups s
235
          return (groups, nodes, instances, cinfo)
236
       )
237

    
238
-- | Converts the output of 'readData' into the internal cluster
239
-- representation.
240
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
241
          -> Result ClusterData
242
parseData (groups, nodes, instances, cinfo) = do
243
  group_data <- groups >>= getGroups
244
  let (group_names, group_idx) = assignIndices group_data
245
  node_data <- nodes >>= getNodes group_names
246
  let (node_names, node_idx) = assignIndices node_data
247
  inst_data <- instances >>= getInstances node_names
248
  let (_, inst_idx) = assignIndices inst_data
249
  ctags <- cinfo >>= getClusterTags
250
  return (ClusterData group_idx node_idx inst_idx ctags)
251

    
252
-- | Top level function for data loading.
253
loadData :: String -- ^ Unix socket to use as source
254
         -> IO (Result ClusterData)
255
loadData = fmap parseData . readData