Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ 6b6e335b

History | View | Annotate | Download (10.1 kB)

1
{-| Implementation of the LUXI loader.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2009, 2010, 2011, 2012 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.JSON
42

    
43
{-# ANN module "HLint: ignore Eta reduce" #-}
44

    
45
-- * Utility functions
46

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

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

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

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

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

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

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

    
95
-- * Data querying functionality
96

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

    
104
-- | The input data for instance query.
105
queryInstancesMsg :: L.LuxiOp
106
queryInstancesMsg =
107
  L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
108
                        "status", "pnode", "snodes", "tags", "oper_ram",
109
                        "be/auto_balance", "disk_template",
110
                        "be/spindle_use"] ()
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", "ipolicy", "tags"] ()
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, su ] = 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
  xsu <- convert "be/spindle_use" su
167
  let inst = Instance.create xname xmem xdisk xvcpus
168
             xrunning xtags xauto_balance xpnode snode xdt xsu
169
  return (xname, inst)
170

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

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

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

    
202
parseNode _ v = fail ("Invalid node query result: " ++ show v)
203

    
204
-- | Parses the cluster tags.
205
getClusterData :: JSValue -> Result ([String], IPolicy)
206
getClusterData (JSObject obj) = do
207
  let errmsg = "Parsing cluster info"
208
      obj' = fromJSObject obj
209
  ctags <- tryFromObj errmsg obj' "tags"
210
  cpol <- tryFromObj errmsg obj' "ipolicy"
211
  return (ctags, cpol)
212

    
213
getClusterData _ = Bad $ "Cannot parse cluster info, not a JSON record"
214

    
215
-- | Parses the cluster groups.
216
getGroups :: JSValue -> Result [(String, Group.Group)]
217
getGroups jsv = extractArray jsv >>= mapM parseGroup
218

    
219
-- | Parses a given group information.
220
parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group)
221
parseGroup [uuid, name, apol, ipol, tags] = do
222
  xname <- annotateResult "Parsing new group" (fromJValWithStatus name)
223
  let convert a = genericConvert "Group" xname a
224
  xuuid <- convert "uuid" uuid
225
  xapol <- convert "alloc_policy" apol
226
  xipol <- convert "ipolicy" ipol
227
  xtags <- convert "tags" tags
228
  return (xuuid, Group.create xname xuuid xapol xipol xtags)
229

    
230
parseGroup v = fail ("Invalid group query result: " ++ show v)
231

    
232
-- * Main loader functionality
233

    
234
-- | Builds the cluster data by querying a given socket name.
235
readData :: String -- ^ Unix socket to use as source
236
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
237
readData master =
238
  E.bracket
239
       (L.getClient master)
240
       L.closeClient
241
       (\s -> do
242
          nodes <- queryNodes s
243
          instances <- queryInstances s
244
          cinfo <- queryClusterInfo s
245
          groups <- queryGroups s
246
          return (groups, nodes, instances, cinfo)
247
       )
248

    
249
-- | Converts the output of 'readData' into the internal cluster
250
-- representation.
251
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
252
          -> Result ClusterData
253
parseData (groups, nodes, instances, cinfo) = do
254
  group_data <- groups >>= getGroups
255
  let (group_names, group_idx) = assignIndices group_data
256
  node_data <- nodes >>= getNodes group_names
257
  let (node_names, node_idx) = assignIndices node_data
258
  inst_data <- instances >>= getInstances node_names
259
  let (_, inst_idx) = assignIndices inst_data
260
  (ctags, cpol) <- cinfo >>= getClusterData
261
  return (ClusterData group_idx node_idx inst_idx ctags cpol)
262

    
263
-- | Top level function for data loading.
264
loadData :: String -- ^ Unix socket to use as source
265
         -> IO (Result ClusterData)
266
loadData = fmap parseData . readData