Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ 92678b3c

History | View | Annotate | Download (9.3 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

    
44
-- * Utility functions
45

    
46
-- | Ensure a given JSValue is actually a JSArray.
47
toArray :: (Monad m) => JSValue -> m [JSValue]
48
toArray v =
49
    case v of
50
      JSArray arr -> return arr
51
      o -> fail ("Invalid input, expected array but got " ++ show o)
52

    
53
-- | Get values behind \"data\" part of the result.
54
getData :: (Monad m) => JSValue -> m JSValue
55
getData v =
56
    case v of
57
      JSObject o ->
58
          case fromJSObject o of
59
            [("data", jsdata), ("fields", _)] -> return jsdata
60
            x -> fail $ "Invalid input, expected two-element list but got "
61
                              ++ show x
62
      x -> fail ("Invalid input, expected dict entry but got " ++ show x)
63

    
64
-- | Get [(status, value)] list for each element queried.
65
toPairs :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]]
66
toPairs (JSArray arr) = do
67
  arr' <- mapM toArray arr -- list of resulting elements
68
  arr'' <- mapM (mapM toArray) arr' -- list of list of [status, value]
69
  return $ map (map (\a -> (a!!0, a!!1))) arr'' -- FIXME: hackish
70
toPairs o = fail ("Invalid input, expected array but got " ++ show o)
71

    
72
-- | Prepare resulting output as parsers expect it.
73
extractArray :: (Monad m) => JSValue -> m [JSValue]
74
extractArray v =  do
75
  arr <- getData v >>= toPairs
76
  return $ map (JSArray. map snd) arr
77

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

    
91
-- * Data querying functionality
92

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

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

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

    
111
-- | The input data for node group query.
112
queryGroupsMsg :: L.LuxiOp
113
queryGroupsMsg =
114
  L.Query L.QRGroup ["uuid", "name", "alloc_policy"] Nothing
115

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

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

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

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

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

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

    
164
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
165

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

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

    
194
parseNode _ v = fail ("Invalid node query result: " ++ show v)
195

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

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

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

    
216
parseGroup v = fail ("Invalid group query result: " ++ show v)
217

    
218
-- * Main loader functionality
219

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

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

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