Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ d12f50b2

History | View | Annotate | Download (9.4 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
    (
28
      loadData
29
    , parseData
30
    ) where
31

    
32
import qualified Control.Exception as E
33
import Text.JSON.Types
34
import qualified Text.JSON
35

    
36
import qualified Ganeti.Luxi as L
37
import Ganeti.HTools.Loader
38
import Ganeti.HTools.Types
39
import qualified Ganeti.HTools.Group as Group
40
import qualified Ganeti.HTools.Node as Node
41
import qualified Ganeti.HTools.Instance as Instance
42
import Ganeti.HTools.Utils (fromJVal, annotateResult, tryFromObj, asJSObject,
43
                            fromObj)
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]
73
extractArray v =
74
  getData v >>= parseQueryResult >>= (return . map (JSArray . map snd))
75

    
76
-- | Annotate errors when converting values with owner/attribute for
77
-- better debugging.
78
genericConvert :: (Text.JSON.JSON a) =>
79
                  String     -- ^ The object type
80
               -> String     -- ^ The object name
81
               -> String     -- ^ The attribute we're trying to convert
82
               -> JSValue    -- ^ The value we try to convert
83
               -> Result a   -- ^ The annotated result
84
genericConvert otype oname oattr =
85
    annotateResult (otype ++ " '" ++ oname ++
86
                    "', error while reading attribute '" ++
87
                    oattr ++ "'") . fromJVal
88

    
89
-- * Data querying functionality
90

    
91
-- | The input data for node query.
92
queryNodesMsg :: L.LuxiOp
93
queryNodesMsg =
94
  L.Query L.QRNode ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
95
                    "ctotal", "offline", "drained", "vm_capable",
96
                    "group.uuid"] ()
97

    
98
-- | The input data for instance query.
99
queryInstancesMsg :: L.LuxiOp
100
queryInstancesMsg =
101
    L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
102
                          "status", "pnode", "snodes", "tags", "oper_ram",
103
                          "be/auto_balance", "disk_template"] ()
104

    
105
-- | The input data for cluster query.
106
queryClusterInfoMsg :: L.LuxiOp
107
queryClusterInfoMsg = L.QueryClusterInfo
108

    
109
-- | The input data for node group query.
110
queryGroupsMsg :: L.LuxiOp
111
queryGroupsMsg =
112
  L.Query L.QRGroup ["uuid", "name", "alloc_policy"] ()
113

    
114
-- | Wraper over 'callMethod' doing node query.
115
queryNodes :: L.Client -> IO (Result JSValue)
116
queryNodes = L.callMethod queryNodesMsg
117

    
118
-- | Wraper over 'callMethod' doing instance query.
119
queryInstances :: L.Client -> IO (Result JSValue)
120
queryInstances = L.callMethod queryInstancesMsg
121

    
122
-- | Wrapper over 'callMethod' doing cluster information query.
123
queryClusterInfo :: L.Client -> IO (Result JSValue)
124
queryClusterInfo = L.callMethod queryClusterInfoMsg
125

    
126
-- | Wrapper over callMethod doing group query.
127
queryGroups :: L.Client -> IO (Result JSValue)
128
queryGroups = L.callMethod queryGroupsMsg
129

    
130
-- | Parse a instance list in JSON format.
131
getInstances :: NameAssoc
132
             -> JSValue
133
             -> Result [(String, Instance.Instance)]
134
getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn)
135

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

    
162
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
163

    
164
-- | Parse a node list in JSON format.
165
getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
166
getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
167

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

    
192
parseNode _ v = fail ("Invalid node query result: " ++ show v)
193

    
194
-- | Parses the cluster tags.
195
getClusterTags :: JSValue -> Result [String]
196
getClusterTags v = do
197
  let errmsg = "Parsing cluster info"
198
  obj <- annotateResult errmsg $ asJSObject v
199
  tryFromObj errmsg (fromJSObject obj) "tags"
200

    
201
-- | Parses the cluster groups.
202
getGroups :: JSValue -> Result [(String, Group.Group)]
203
getGroups jsv = extractArray jsv >>= mapM parseGroup
204

    
205
-- | Parses a given group information.
206
parseGroup :: JSValue -> Result (String, Group.Group)
207
parseGroup (JSArray [uuid, name, apol]) = do
208
  xname <- annotateResult "Parsing new group" (fromJVal name)
209
  let convert a = genericConvert "Group" xname a
210
  xuuid <- convert "uuid" uuid
211
  xapol <- convert "alloc_policy" apol
212
  return (xuuid, Group.create xname xuuid xapol)
213

    
214
parseGroup v = fail ("Invalid group query result: " ++ show v)
215

    
216
-- * Main loader functionality
217

    
218
-- | Builds the cluster data by querying a given socket name.
219
readData :: String -- ^ Unix socket to use as source
220
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
221
readData master =
222
  E.bracket
223
       (L.getClient master)
224
       L.closeClient
225
       (\s -> do
226
          nodes <- queryNodes s
227
          instances <- queryInstances s
228
          cinfo <- queryClusterInfo s
229
          groups <- queryGroups s
230
          return (groups, nodes, instances, cinfo)
231
       )
232

    
233
-- | Converts the output of 'readData' into the internal cluster
234
-- representation.
235
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
236
          -> Result ClusterData
237
parseData (groups, nodes, instances, cinfo) = do
238
  group_data <- groups >>= getGroups
239
  let (group_names, group_idx) = assignIndices group_data
240
  node_data <- nodes >>= getNodes group_names
241
  let (node_names, node_idx) = assignIndices node_data
242
  inst_data <- instances >>= getInstances node_names
243
  let (_, inst_idx) = assignIndices inst_data
244
  ctags <- cinfo >>= getClusterTags
245
  return (ClusterData group_idx node_idx inst_idx ctags)
246

    
247
-- | Top level function for data loading.
248
loadData :: String -- ^ Unix socket to use as source
249
         -> IO (Result ClusterData)
250
loadData = fmap parseData . readData