Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Backend / Luxi.hs @ 879d9290

History | View | Annotate | Download (10.2 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.Backend.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 Ganeti.BasicTypes
36
import qualified Ganeti.Luxi as L
37
import qualified Ganeti.Query.Language as Qlang
38
import Ganeti.HTools.Loader
39
import Ganeti.HTools.Types
40
import qualified Ganeti.HTools.Group as Group
41
import qualified Ganeti.HTools.Node as Node
42
import qualified Ganeti.HTools.Instance as Instance
43
import Ganeti.JSON
44

    
45
{-# ANN module "HLint: ignore Eta reduce" #-}
46

    
47
-- * Utility functions
48

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

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

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

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

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

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

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

    
97
-- * Data querying functionality
98

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

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

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

    
118
-- | The input data for node group query.
119
queryGroupsMsg :: L.LuxiOp
120
queryGroupsMsg =
121
  L.Query Qlang.QRGroup ["uuid", "name", "alloc_policy", "ipolicy", "tags"]
122
   Qlang.EmptyFilter
123

    
124
-- | Wraper over 'callMethod' doing node query.
125
queryNodes :: L.Client -> IO (Result JSValue)
126
queryNodes = L.callMethod queryNodesMsg
127

    
128
-- | Wraper over 'callMethod' doing instance query.
129
queryInstances :: L.Client -> IO (Result JSValue)
130
queryInstances = L.callMethod queryInstancesMsg
131

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

    
136
-- | Wrapper over callMethod doing group query.
137
queryGroups :: L.Client -> IO (Result JSValue)
138
queryGroups = L.callMethod queryGroupsMsg
139

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

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

    
174
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
175

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

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

    
205
parseNode _ v = fail ("Invalid node query result: " ++ show v)
206

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

    
216
getClusterData _ = Bad "Cannot parse cluster info, not a JSON record"
217

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

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

    
233
parseGroup v = fail ("Invalid group query result: " ++ show v)
234

    
235
-- * Main loader functionality
236

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

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

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