Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / Backend / Luxi.hs @ 96f9b0a6

History | View | Annotate | Download (10.8 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
-- | Annotate errors when converting values with owner/attribute for
87
-- better debugging.
88
genericConvert :: (Text.JSON.JSON a) =>
89
                  String             -- ^ The object type
90
               -> String             -- ^ The object name
91
               -> String             -- ^ The attribute we're trying to convert
92
               -> (JSValue, JSValue) -- ^ The value we're trying to convert
93
               -> Result a           -- ^ The annotated result
94
genericConvert otype oname oattr =
95
  annotateResult (otype ++ " '" ++ oname ++
96
                  "', error while reading attribute '" ++
97
                  oattr ++ "'") . fromJValWithStatus
98

    
99
-- * Data querying functionality
100

    
101
-- | The input data for node query.
102
queryNodesMsg :: L.LuxiOp
103
queryNodesMsg =
104
  L.Query (Qlang.ItemTypeOpCode Qlang.QRNode)
105
     ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
106
      "ctotal", "offline", "drained", "vm_capable",
107
      "ndp/spindle_count", "group.uuid", "tags",
108
      "ndp/exclusive_storage", "sptotal", "spfree"] Qlang.EmptyFilter
109

    
110
-- | The input data for instance query.
111
queryInstancesMsg :: L.LuxiOp
112
queryInstancesMsg =
113
  L.Query (Qlang.ItemTypeOpCode Qlang.QRInstance)
114
     ["name", "disk_usage", "be/memory", "be/vcpus",
115
      "status", "pnode", "snodes", "tags", "oper_ram",
116
      "be/auto_balance", "disk_template",
117
      "be/spindle_use"] Qlang.EmptyFilter
118

    
119
-- | The input data for cluster query.
120
queryClusterInfoMsg :: L.LuxiOp
121
queryClusterInfoMsg = L.QueryClusterInfo
122

    
123
-- | The input data for node group query.
124
queryGroupsMsg :: L.LuxiOp
125
queryGroupsMsg =
126
  L.Query (Qlang.ItemTypeOpCode Qlang.QRGroup)
127
     ["uuid", "name", "alloc_policy", "ipolicy", "tags"]
128
     Qlang.EmptyFilter
129

    
130
-- | Wraper over 'callMethod' doing node query.
131
queryNodes :: L.Client -> IO (Result JSValue)
132
queryNodes = liftM errToResult . L.callMethod queryNodesMsg
133

    
134
-- | Wraper over 'callMethod' doing instance query.
135
queryInstances :: L.Client -> IO (Result JSValue)
136
queryInstances = liftM errToResult . L.callMethod queryInstancesMsg
137

    
138
-- | Wrapper over 'callMethod' doing cluster information query.
139
queryClusterInfo :: L.Client -> IO (Result JSValue)
140
queryClusterInfo = liftM errToResult . L.callMethod queryClusterInfoMsg
141

    
142
-- | Wrapper over callMethod doing group query.
143
queryGroups :: L.Client -> IO (Result JSValue)
144
queryGroups = liftM errToResult . L.callMethod queryGroupsMsg
145

    
146
-- | Parse a instance list in JSON format.
147
getInstances :: NameAssoc
148
             -> JSValue
149
             -> Result [(String, Instance.Instance)]
150
getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
151

    
152
-- | Construct an instance from a JSON object.
153
parseInstance :: NameAssoc
154
              -> [(JSValue, JSValue)]
155
              -> Result (String, Instance.Instance)
156
parseInstance ktn [ name, disk, mem, vcpus
157
                  , status, pnode, snodes, tags, oram
158
                  , auto_balance, disk_template, su ] = do
159
  xname <- annotateResult "Parsing new instance" (fromJValWithStatus name)
160
  let convert a = genericConvert "Instance" xname a
161
  xdisk <- convert "disk_usage" disk
162
  xmem <- case oram of -- FIXME: remove the "guessing"
163
            (_, JSRational _ _) -> convert "oper_ram" oram
164
            _ -> convert "be/memory" mem
165
  xvcpus <- convert "be/vcpus" vcpus
166
  xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
167
  xsnodes <- convert "snodes" snodes::Result [String]
168
  snode <- case xsnodes of
169
             [] -> return Node.noSecondary
170
             x:_ -> lookupNode ktn xname x
171
  xrunning <- convert "status" status
172
  xtags <- convert "tags" tags
173
  xauto_balance <- convert "auto_balance" auto_balance
174
  xdt <- convert "disk_template" disk_template
175
  xsu <- convert "be/spindle_use" su
176
  let inst = Instance.create xname xmem xdisk [Instance.Disk xdisk Nothing]
177
             xvcpus xrunning xtags xauto_balance xpnode snode xdt xsu []
178
  return (xname, inst)
179

    
180
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
181

    
182
-- | Parse a node list in JSON format.
183
getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
184
getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
185

    
186
-- | Construct a node from a JSON object.
187
parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node)
188
parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree
189
              , ctotal, offline, drained, vm_capable, spindles, g_uuid
190
              , tags, excl_stor, sptotal, spfree ]
191
    = do
192
  xname <- annotateResult "Parsing new node" (fromJValWithStatus name)
193
  let convert a = genericConvert "Node" xname a
194
  xoffline <- convert "offline" offline
195
  xdrained <- convert "drained" drained
196
  xvm_capable <- convert "vm_capable" vm_capable
197
  xgdx   <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
198
  xtags <- convert "tags" tags
199
  xexcl_stor <- convert "exclusive_storage" excl_stor
200
  let live = not xoffline && not xdrained && xvm_capable
201
      lvconvert def n d = eitherLive live def $ convert n d
202
  xsptotal <- if xexcl_stor
203
              then lvconvert 0 "sptotal" sptotal
204
              else convert "spindles" spindles
205
  xspfree <- lvconvert 0 "spfree" spfree
206
  xmtotal <- lvconvert 0.0 "mtotal" mtotal
207
  xmnode <- lvconvert 0 "mnode" mnode
208
  xmfree <- lvconvert 0 "mfree" mfree
209
  xdtotal <- lvconvert 0.0 "dtotal" dtotal
210
  xdfree <- lvconvert 0 "dfree" dfree
211
  xctotal <- lvconvert 0.0 "ctotal" ctotal
212
  let node = flip Node.setNodeTags xtags $
213
             Node.create xname xmtotal xmnode xmfree xdtotal xdfree
214
             xctotal (not live) xsptotal xspfree xgdx xexcl_stor
215
  return (xname, node)
216

    
217
parseNode _ v = fail ("Invalid node query result: " ++ show v)
218

    
219
-- | Parses the cluster tags.
220
getClusterData :: JSValue -> Result ([String], IPolicy, String)
221
getClusterData (JSObject obj) = do
222
  let errmsg = "Parsing cluster info"
223
      obj' = fromJSObject obj
224
  ctags <- tryFromObj errmsg obj' "tags"
225
  cpol <- tryFromObj errmsg obj' "ipolicy"
226
  master <- tryFromObj errmsg obj' "master"
227
  return (ctags, cpol, master)
228

    
229
getClusterData _ = Bad "Cannot parse cluster info, not a JSON record"
230

    
231
-- | Parses the cluster groups.
232
getGroups :: JSValue -> Result [(String, Group.Group)]
233
getGroups jsv = extractArray jsv >>= mapM parseGroup
234

    
235
-- | Parses a given group information.
236
parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group)
237
parseGroup [uuid, name, apol, ipol, tags] = do
238
  xname <- annotateResult "Parsing new group" (fromJValWithStatus name)
239
  let convert a = genericConvert "Group" xname a
240
  xuuid <- convert "uuid" uuid
241
  xapol <- convert "alloc_policy" apol
242
  xipol <- convert "ipolicy" ipol
243
  xtags <- convert "tags" tags
244
  -- TODO: parse networks to which this group is connected
245
  return (xuuid, Group.create xname xuuid xapol [] xipol xtags)
246

    
247
parseGroup v = fail ("Invalid group query result: " ++ show v)
248

    
249
-- * Main loader functionality
250

    
251
-- | Builds the cluster data by querying a given socket name.
252
readData :: String -- ^ Unix socket to use as source
253
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
254
readData master =
255
  E.bracket
256
       (L.getClient master)
257
       L.closeClient
258
       (\s -> do
259
          nodes <- queryNodes s
260
          instances <- queryInstances s
261
          cinfo <- queryClusterInfo s
262
          groups <- queryGroups s
263
          return (groups, nodes, instances, cinfo)
264
       )
265

    
266
-- | Converts the output of 'readData' into the internal cluster
267
-- representation.
268
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
269
          -> Result ClusterData
270
parseData (groups, nodes, instances, cinfo) = do
271
  group_data <- groups >>= getGroups
272
  let (group_names, group_idx) = assignIndices group_data
273
  node_data <- nodes >>= getNodes group_names
274
  let (node_names, node_idx) = assignIndices node_data
275
  inst_data <- instances >>= getInstances node_names
276
  let (_, inst_idx) = assignIndices inst_data
277
  (ctags, cpol, master) <- cinfo >>= getClusterData
278
  node_idx' <- setMaster node_names node_idx master
279
  return (ClusterData group_idx node_idx' inst_idx ctags cpol)
280

    
281
-- | Top level function for data loading.
282
loadData :: String -- ^ Unix socket to use as source
283
         -> IO (Result ClusterData)
284
loadData = fmap parseData . readData