Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ 3603605a

History | View | Annotate | Download (9.7 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
{-# ANN module "HLint: ignore Eta reduce" #-}
45

    
46
-- * Utility functions
47

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

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

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

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

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

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

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

    
96
-- * Data querying functionality
97

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

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

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

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

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

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

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

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

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

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

    
170
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
171

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

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

    
200
parseNode _ v = fail ("Invalid node query result: " ++ show v)
201

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

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

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

    
222
parseGroup v = fail ("Invalid group query result: " ++ show v)
223

    
224
-- * Main loader functionality
225

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

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

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