Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / Backend / Luxi.hs @ 0ec8cce2

History | View | Annotate | Download (11.6 kB)

1
{-| Implementation of the LUXI loader.
2

    
3
-}
4

    
5
{-
6

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

    
31
import qualified Control.Exception as E
32
import Control.Monad (liftM)
33
import Text.JSON.Types
34
import qualified Text.JSON
35

    
36
import Ganeti.BasicTypes
37
import Ganeti.Errors
38
import qualified Ganeti.Luxi as L
39
import qualified Ganeti.Query.Language as Qlang
40
import Ganeti.HTools.Loader
41
import Ganeti.HTools.Types
42
import qualified Ganeti.HTools.Group as Group
43
import qualified Ganeti.HTools.Node as Node
44
import qualified Ganeti.HTools.Instance as Instance
45
import Ganeti.JSON
46

    
47
{-# ANN module "HLint: ignore Eta reduce" #-}
48

    
49
-- * Utility functions
50

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

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

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

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

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

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

    
86
annotateConvert :: String -> String -> String -> Result a -> Result a
87
annotateConvert otype oname oattr =
88
  annotateResult $ otype ++ " '" ++ oname ++
89
    "', error while reading attribute '" ++ oattr ++ "'"
90

    
91
-- | Annotate errors when converting values with owner/attribute for
92
-- better debugging.
93
genericConvert :: (Text.JSON.JSON a) =>
94
                  String             -- ^ The object type
95
               -> String             -- ^ The object name
96
               -> String             -- ^ The attribute we're trying to convert
97
               -> (JSValue, JSValue) -- ^ The value we're trying to convert
98
               -> Result a           -- ^ The annotated result
99
genericConvert otype oname oattr =
100
  annotateConvert otype oname oattr . fromJValWithStatus
101

    
102
convertArrayMaybe :: (Text.JSON.JSON a) =>
103
                  String             -- ^ The object type
104
               -> String             -- ^ The object name
105
               -> String             -- ^ The attribute we're trying to convert
106
               -> (JSValue, JSValue) -- ^ The value we're trying to convert
107
               -> Result [Maybe a]   -- ^ The annotated result
108
convertArrayMaybe otype oname oattr (st, v) = do
109
  st' <- fromJVal st
110
  Qlang.checkRS st' v >>=
111
    annotateConvert otype oname oattr . arrayMaybeFromJVal
112

    
113
-- * Data querying functionality
114

    
115
-- | The input data for node query.
116
queryNodesMsg :: L.LuxiOp
117
queryNodesMsg =
118
  L.Query (Qlang.ItemTypeOpCode Qlang.QRNode)
119
     ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
120
      "ctotal", "offline", "drained", "vm_capable",
121
      "ndp/spindle_count", "group.uuid", "tags",
122
      "ndp/exclusive_storage", "sptotal", "spfree"] Qlang.EmptyFilter
123

    
124
-- | The input data for instance query.
125
queryInstancesMsg :: L.LuxiOp
126
queryInstancesMsg =
127
  L.Query (Qlang.ItemTypeOpCode Qlang.QRInstance)
128
     ["name", "disk_usage", "be/memory", "be/vcpus",
129
      "status", "pnode", "snodes", "tags", "oper_ram",
130
      "be/auto_balance", "disk_template",
131
      "be/spindle_use", "disk.sizes", "disk.spindles"] Qlang.EmptyFilter
132

    
133
-- | The input data for cluster query.
134
queryClusterInfoMsg :: L.LuxiOp
135
queryClusterInfoMsg = L.QueryClusterInfo
136

    
137
-- | The input data for node group query.
138
queryGroupsMsg :: L.LuxiOp
139
queryGroupsMsg =
140
  L.Query (Qlang.ItemTypeOpCode Qlang.QRGroup)
141
     ["uuid", "name", "alloc_policy", "ipolicy", "tags"]
142
     Qlang.EmptyFilter
143

    
144
-- | Wraper over 'callMethod' doing node query.
145
queryNodes :: L.Client -> IO (Result JSValue)
146
queryNodes = liftM errToResult . L.callMethod queryNodesMsg
147

    
148
-- | Wraper over 'callMethod' doing instance query.
149
queryInstances :: L.Client -> IO (Result JSValue)
150
queryInstances = liftM errToResult . L.callMethod queryInstancesMsg
151

    
152
-- | Wrapper over 'callMethod' doing cluster information query.
153
queryClusterInfo :: L.Client -> IO (Result JSValue)
154
queryClusterInfo = liftM errToResult . L.callMethod queryClusterInfoMsg
155

    
156
-- | Wrapper over callMethod doing group query.
157
queryGroups :: L.Client -> IO (Result JSValue)
158
queryGroups = liftM errToResult . L.callMethod queryGroupsMsg
159

    
160
-- | Parse a instance list in JSON format.
161
getInstances :: NameAssoc
162
             -> JSValue
163
             -> Result [(String, Instance.Instance)]
164
getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
165

    
166
-- | Construct an instance from a JSON object.
167
parseInstance :: NameAssoc
168
              -> [(JSValue, JSValue)]
169
              -> Result (String, Instance.Instance)
170
parseInstance ktn [ name, disk, mem, vcpus
171
                  , status, pnode, snodes, tags, oram
172
                  , auto_balance, disk_template, su
173
                  , dsizes, dspindles ] = do
174
  xname <- annotateResult "Parsing new instance" (fromJValWithStatus name)
175
  let convert a = genericConvert "Instance" xname a
176
  xdisk <- convert "disk_usage" disk
177
  xmem <- case oram of -- FIXME: remove the "guessing"
178
            (_, JSRational _ _) -> convert "oper_ram" oram
179
            _ -> convert "be/memory" mem
180
  xvcpus <- convert "be/vcpus" vcpus
181
  xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
182
  xsnodes <- convert "snodes" snodes::Result [String]
183
  snode <- case xsnodes of
184
             [] -> return Node.noSecondary
185
             x:_ -> lookupNode ktn xname x
186
  xrunning <- convert "status" status
187
  xtags <- convert "tags" tags
188
  xauto_balance <- convert "auto_balance" auto_balance
189
  xdt <- convert "disk_template" disk_template
190
  xsu <- convert "be/spindle_use" su
191
  xdsizes <- convert "disk.sizes" dsizes
192
  xdspindles <- convertArrayMaybe "Instance" xname "disk.spindles" dspindles
193
  let disks = zipWith Instance.Disk xdsizes xdspindles
194
      inst = Instance.create xname xmem xdisk disks
195
             xvcpus xrunning xtags xauto_balance xpnode snode xdt xsu []
196
  return (xname, inst)
197

    
198
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
199

    
200
-- | Parse a node list in JSON format.
201
getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
202
getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
203

    
204
-- | Construct a node from a JSON object.
205
parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node)
206
parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree
207
              , ctotal, offline, drained, vm_capable, spindles, g_uuid
208
              , tags, excl_stor, sptotal, spfree ]
209
    = do
210
  xname <- annotateResult "Parsing new node" (fromJValWithStatus name)
211
  let convert a = genericConvert "Node" xname a
212
  xoffline <- convert "offline" offline
213
  xdrained <- convert "drained" drained
214
  xvm_capable <- convert "vm_capable" vm_capable
215
  xgdx   <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
216
  xtags <- convert "tags" tags
217
  xexcl_stor <- convert "exclusive_storage" excl_stor
218
  let live = not xoffline && not xdrained && xvm_capable
219
      lvconvert def n d = eitherLive live def $ convert n d
220
  xsptotal <- if xexcl_stor
221
              then lvconvert 0 "sptotal" sptotal
222
              else convert "spindles" spindles
223
  xspfree <- lvconvert 0 "spfree" spfree
224
  xmtotal <- lvconvert 0.0 "mtotal" mtotal
225
  xmnode <- lvconvert 0 "mnode" mnode
226
  xmfree <- lvconvert 0 "mfree" mfree
227
  xdtotal <- lvconvert 0.0 "dtotal" dtotal
228
  xdfree <- lvconvert 0 "dfree" dfree
229
  xctotal <- lvconvert 0.0 "ctotal" ctotal
230
  let node = flip Node.setNodeTags xtags $
231
             Node.create xname xmtotal xmnode xmfree xdtotal xdfree
232
             xctotal (not live) xsptotal xspfree xgdx xexcl_stor
233
  return (xname, node)
234

    
235
parseNode _ v = fail ("Invalid node query result: " ++ show v)
236

    
237
-- | Parses the cluster tags.
238
getClusterData :: JSValue -> Result ([String], IPolicy, String)
239
getClusterData (JSObject obj) = do
240
  let errmsg = "Parsing cluster info"
241
      obj' = fromJSObject obj
242
  ctags <- tryFromObj errmsg obj' "tags"
243
  cpol <- tryFromObj errmsg obj' "ipolicy"
244
  master <- tryFromObj errmsg obj' "master"
245
  return (ctags, cpol, master)
246

    
247
getClusterData _ = Bad "Cannot parse cluster info, not a JSON record"
248

    
249
-- | Parses the cluster groups.
250
getGroups :: JSValue -> Result [(String, Group.Group)]
251
getGroups jsv = extractArray jsv >>= mapM parseGroup
252

    
253
-- | Parses a given group information.
254
parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group)
255
parseGroup [uuid, name, apol, ipol, tags] = do
256
  xname <- annotateResult "Parsing new group" (fromJValWithStatus name)
257
  let convert a = genericConvert "Group" xname a
258
  xuuid <- convert "uuid" uuid
259
  xapol <- convert "alloc_policy" apol
260
  xipol <- convert "ipolicy" ipol
261
  xtags <- convert "tags" tags
262
  -- TODO: parse networks to which this group is connected
263
  return (xuuid, Group.create xname xuuid xapol [] xipol xtags)
264

    
265
parseGroup v = fail ("Invalid group query result: " ++ show v)
266

    
267
-- * Main loader functionality
268

    
269
-- | Builds the cluster data by querying a given socket name.
270
readData :: String -- ^ Unix socket to use as source
271
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
272
readData master =
273
  E.bracket
274
       (L.getClient master)
275
       L.closeClient
276
       (\s -> do
277
          nodes <- queryNodes s
278
          instances <- queryInstances s
279
          cinfo <- queryClusterInfo s
280
          groups <- queryGroups s
281
          return (groups, nodes, instances, cinfo)
282
       )
283

    
284
-- | Converts the output of 'readData' into the internal cluster
285
-- representation.
286
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
287
          -> Result ClusterData
288
parseData (groups, nodes, instances, cinfo) = do
289
  group_data <- groups >>= getGroups
290
  let (group_names, group_idx) = assignIndices group_data
291
  node_data <- nodes >>= getNodes group_names
292
  let (node_names, node_idx) = assignIndices node_data
293
  inst_data <- instances >>= getInstances node_names
294
  let (_, inst_idx) = assignIndices inst_data
295
  (ctags, cpol, master) <- cinfo >>= getClusterData
296
  node_idx' <- setMaster node_names node_idx master
297
  return (ClusterData group_idx node_idx' inst_idx ctags cpol)
298

    
299
-- | Top level function for data loading.
300
loadData :: String -- ^ Unix socket to use as source
301
         -> IO (Result ClusterData)
302
loadData = fmap parseData . readData