Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Luxi.hs @ 6880526c

History | View | Annotate | Download (7.9 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
-- | Annotate errors when converting values with owner/attribute for
54
-- better debugging.
55
genericConvert :: (Text.JSON.JSON a) =>
56
                  String     -- ^ The object type
57
               -> String     -- ^ The object name
58
               -> String     -- ^ The attribute we're trying to convert
59
               -> JSValue    -- ^ The value we try to convert
60
               -> Result a   -- ^ The annotated result
61
genericConvert otype oname oattr =
62
    annotateResult (otype ++ " '" ++ oname ++ "', attribute '" ++
63
                    oattr ++ "'") . fromJVal
64

    
65
-- * Data querying functionality
66

    
67
-- | The input data for node query.
68
queryNodesMsg :: L.LuxiOp
69
queryNodesMsg =
70
  L.QueryNodes [] ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
71
                   "ctotal", "offline", "drained", "vm_capable",
72
                   "group.uuid"] False
73

    
74
-- | The input data for instance query.
75
queryInstancesMsg :: L.LuxiOp
76
queryInstancesMsg =
77
  L.QueryInstances [] ["name", "disk_usage", "be/memory", "be/vcpus",
78
                       "status", "pnode", "snodes", "tags", "oper_ram",
79
                       "be/auto_balance"] False
80

    
81
-- | The input data for cluster query.
82
queryClusterInfoMsg :: L.LuxiOp
83
queryClusterInfoMsg = L.QueryClusterInfo
84

    
85
-- | The input data for node group query.
86
queryGroupsMsg :: L.LuxiOp
87
queryGroupsMsg =
88
  L.QueryGroups [] ["uuid", "name", "alloc_policy"] False
89

    
90
-- | Wraper over callMethod doing node query.
91
queryNodes :: L.Client -> IO (Result JSValue)
92
queryNodes = L.callMethod queryNodesMsg
93

    
94
-- | Wraper over callMethod doing instance query.
95
queryInstances :: L.Client -> IO (Result JSValue)
96
queryInstances = L.callMethod queryInstancesMsg
97

    
98
queryClusterInfo :: L.Client -> IO (Result JSValue)
99
queryClusterInfo = L.callMethod queryClusterInfoMsg
100

    
101
-- | Wrapper over callMethod doing group query.
102
queryGroups :: L.Client -> IO (Result JSValue)
103
queryGroups = L.callMethod queryGroupsMsg
104

    
105
-- | Parse a instance list in JSON format.
106
getInstances :: NameAssoc
107
             -> JSValue
108
             -> Result [(String, Instance.Instance)]
109
getInstances ktn arr = toArray arr >>= mapM (parseInstance ktn)
110

    
111
-- | Construct an instance from a JSON object.
112
parseInstance :: NameAssoc
113
              -> JSValue
114
              -> Result (String, Instance.Instance)
115
parseInstance ktn (JSArray [ name, disk, mem, vcpus
116
                           , status, pnode, snodes, tags, oram
117
                           , auto_balance ]) = do
118
  xname <- annotateResult "Parsing new instance" (fromJVal name)
119
  let convert a = genericConvert "Instance" xname a
120
  xdisk <- convert "disk_usage" disk
121
  xmem <- (case oram of
122
             JSRational _ _ -> convert "oper_ram" oram
123
             _ -> convert "be/memory" mem)
124
  xvcpus <- convert "be/vcpus" vcpus
125
  xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
126
  xsnodes <- convert "snodes" snodes::Result [JSString]
127
  snode <- (if null xsnodes then return Node.noSecondary
128
            else lookupNode ktn xname (fromJSString $ head xsnodes))
129
  xrunning <- convert "status" status
130
  xtags <- convert "tags" tags
131
  xauto_balance <- convert "auto_balance" auto_balance
132
  let inst = Instance.create xname xmem xdisk xvcpus
133
             xrunning xtags xauto_balance xpnode snode
134
  return (xname, inst)
135

    
136
parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
137

    
138
-- | Parse a node list in JSON format.
139
getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)]
140
getNodes ktg arr = toArray arr >>= mapM (parseNode ktg)
141

    
142
-- | Construct a node from a JSON object.
143
parseNode :: NameAssoc -> JSValue -> Result (String, Node.Node)
144
parseNode ktg (JSArray [ name, mtotal, mnode, mfree, dtotal, dfree
145
                       , ctotal, offline, drained, vm_capable, g_uuid ])
146
    = do
147
  xname <- annotateResult "Parsing new node" (fromJVal name)
148
  let convert a = genericConvert "Node" xname a
149
  xoffline <- convert "offline" offline
150
  xdrained <- convert "drained" drained
151
  xvm_capable <- convert "vm_capable" vm_capable
152
  xgdx   <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
153
  node <- (if xoffline || xdrained || not xvm_capable
154
           then return $ Node.create xname 0 0 0 0 0 0 True xgdx
155
           else do
156
             xmtotal  <- convert "mtotal" mtotal
157
             xmnode   <- convert "mnode" mnode
158
             xmfree   <- convert "mfree" mfree
159
             xdtotal  <- convert "dtotal" dtotal
160
             xdfree   <- convert "dfree" dfree
161
             xctotal  <- convert "ctotal" ctotal
162
             return $ Node.create xname xmtotal xmnode xmfree
163
                    xdtotal xdfree xctotal False xgdx)
164
  return (xname, node)
165

    
166
parseNode _ v = fail ("Invalid node query result: " ++ show v)
167

    
168
getClusterTags :: JSValue -> Result [String]
169
getClusterTags v = do
170
  let errmsg = "Parsing cluster info"
171
  obj <- annotateResult errmsg $ asJSObject v
172
  tryFromObj errmsg (fromJSObject obj) "tags"
173

    
174
getGroups :: JSValue -> Result [(String, Group.Group)]
175
getGroups arr = toArray arr >>= mapM parseGroup
176

    
177
parseGroup :: JSValue -> Result (String, Group.Group)
178
parseGroup (JSArray [ uuid, name, apol ]) = do
179
  xname <- annotateResult "Parsing new group" (fromJVal name)
180
  let convert a = genericConvert "Group" xname a
181
  xuuid <- convert "uuid" uuid
182
  xapol <- convert "alloc_policy" apol
183
  return (xuuid, Group.create xname xuuid xapol)
184

    
185
parseGroup v = fail ("Invalid group query result: " ++ show v)
186

    
187
-- * Main loader functionality
188

    
189
-- | Builds the cluster data from an URL.
190
readData :: String -- ^ Unix socket to use as source
191
         -> IO (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
192
readData master =
193
  E.bracket
194
       (L.getClient master)
195
       L.closeClient
196
       (\s -> do
197
          nodes <- queryNodes s
198
          instances <- queryInstances s
199
          cinfo <- queryClusterInfo s
200
          groups <- queryGroups s
201
          return (groups, nodes, instances, cinfo)
202
       )
203

    
204
parseData :: (Result JSValue, Result JSValue, Result JSValue, Result JSValue)
205
          -> Result ClusterData
206
parseData (groups, nodes, instances, cinfo) = do
207
  group_data <- groups >>= getGroups
208
  let (group_names, group_idx) = assignIndices group_data
209
  node_data <- nodes >>= getNodes group_names
210
  let (node_names, node_idx) = assignIndices node_data
211
  inst_data <- instances >>= getInstances node_names
212
  let (_, inst_idx) = assignIndices inst_data
213
  ctags <- cinfo >>= getClusterTags
214
  return (ClusterData group_idx node_idx inst_idx ctags)
215

    
216
-- | Top level function for data loading
217
loadData :: String -- ^ Unix socket to use as source
218
         -> IO (Result ClusterData)
219
loadData = fmap parseData . readData