Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / Backend / Rapi.hs @ 3daaab6c

History | View | Annotate | Download (9.2 kB)

1 a7654563 Iustin Pop
{-| Implementation of the RAPI client interface.
2 a7654563 Iustin Pop
3 a7654563 Iustin Pop
-}
4 a7654563 Iustin Pop
5 e2fa2baf Iustin Pop
{-
6 e2fa2baf Iustin Pop
7 72747d91 Iustin Pop
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
8 e2fa2baf Iustin Pop
9 e2fa2baf Iustin Pop
This program is free software; you can redistribute it and/or modify
10 e2fa2baf Iustin Pop
it under the terms of the GNU General Public License as published by
11 e2fa2baf Iustin Pop
the Free Software Foundation; either version 2 of the License, or
12 e2fa2baf Iustin Pop
(at your option) any later version.
13 e2fa2baf Iustin Pop
14 e2fa2baf Iustin Pop
This program is distributed in the hope that it will be useful, but
15 e2fa2baf Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
16 e2fa2baf Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 e2fa2baf Iustin Pop
General Public License for more details.
18 e2fa2baf Iustin Pop
19 e2fa2baf Iustin Pop
You should have received a copy of the GNU General Public License
20 e2fa2baf Iustin Pop
along with this program; if not, write to the Free Software
21 e2fa2baf Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 e2fa2baf Iustin Pop
02110-1301, USA.
23 e2fa2baf Iustin Pop
24 e2fa2baf Iustin Pop
-}
25 e2fa2baf Iustin Pop
26 c478f837 Iustin Pop
{-# LANGUAGE BangPatterns, CPP #-}
27 c9224fa4 Iustin Pop
28 879d9290 Iustin Pop
module Ganeti.HTools.Backend.Rapi
29 ebf38064 Iustin Pop
  ( loadData
30 ebf38064 Iustin Pop
  , parseData
31 ebf38064 Iustin Pop
  ) where
32 a7654563 Iustin Pop
33 30d25dd8 Iustin Pop
import Control.Exception
34 d575c755 Iustin Pop
import Data.List (isPrefixOf)
35 3986684e Iustin Pop
import Data.Maybe (fromMaybe)
36 a7654563 Iustin Pop
import Network.Curl
37 b8b9a53c Iustin Pop
import Network.Curl.Types ()
38 a7654563 Iustin Pop
import Control.Monad
39 28f19313 Iustin Pop
import Text.JSON (JSObject, fromJSObject, decodeStrict)
40 6402a260 Iustin Pop
import Text.JSON.Types (JSValue(..))
41 a7654563 Iustin Pop
import Text.Printf (printf)
42 d575c755 Iustin Pop
import System.FilePath
43 040afc35 Iustin Pop
44 01e52493 Iustin Pop
import Ganeti.BasicTypes
45 040afc35 Iustin Pop
import Ganeti.HTools.Loader
46 e4c5beaf Iustin Pop
import Ganeti.HTools.Types
47 f3baf5ef Iustin Pop
import Ganeti.JSON
48 a679e9dc Iustin Pop
import qualified Ganeti.HTools.Group as Group
49 040afc35 Iustin Pop
import qualified Ganeti.HTools.Node as Node
50 040afc35 Iustin Pop
import qualified Ganeti.HTools.Instance as Instance
51 a69ff623 Iustin Pop
import qualified Ganeti.Constants as C
52 a7654563 Iustin Pop
53 3603605a Iustin Pop
{-# ANN module "HLint: ignore Eta reduce" #-}
54 3603605a Iustin Pop
55 d575c755 Iustin Pop
-- | File method prefix.
56 d575c755 Iustin Pop
filePrefix :: String
57 d575c755 Iustin Pop
filePrefix = "file://"
58 d575c755 Iustin Pop
59 c478f837 Iustin Pop
-- | Read an URL via curl and return the body if successful.
60 c478f837 Iustin Pop
getUrl :: (Monad m) => String -> IO (m String)
61 c478f837 Iustin Pop
62 4cd79ca8 Iustin Pop
-- | Connection timeout (when using non-file methods).
63 4cd79ca8 Iustin Pop
connTimeout :: Long
64 4cd79ca8 Iustin Pop
connTimeout = 15
65 4cd79ca8 Iustin Pop
66 4cd79ca8 Iustin Pop
-- | The default timeout for queries (when using non-file methods).
67 4cd79ca8 Iustin Pop
queryTimeout :: Long
68 4cd79ca8 Iustin Pop
queryTimeout = 60
69 4cd79ca8 Iustin Pop
70 525bfb36 Iustin Pop
-- | The curl options we use.
71 4d8e5008 Iustin Pop
curlOpts :: [CurlOption]
72 4d8e5008 Iustin Pop
curlOpts = [ CurlSSLVerifyPeer False
73 4d8e5008 Iustin Pop
           , CurlSSLVerifyHost 0
74 4cd79ca8 Iustin Pop
           , CurlTimeout queryTimeout
75 4cd79ca8 Iustin Pop
           , CurlConnectTimeout connTimeout
76 4d8e5008 Iustin Pop
           ]
77 4d8e5008 Iustin Pop
78 a7654563 Iustin Pop
getUrl url = do
79 c9224fa4 Iustin Pop
  (code, !body) <- curlGetString url curlOpts
80 a7654563 Iustin Pop
  return (case code of
81 ba00ad4d Iustin Pop
            CurlOK -> return body
82 ba00ad4d Iustin Pop
            _ -> fail $ printf "Curl error for '%s', error %s"
83 aab26f2d Iustin Pop
                 url (show code))
84 aab26f2d Iustin Pop
85 d575c755 Iustin Pop
-- | Helper to convert I/O errors in 'Bad' values.
86 d575c755 Iustin Pop
ioErrToResult :: IO a -> IO (Result a)
87 d575c755 Iustin Pop
ioErrToResult ioaction =
88 b9612abb Iustin Pop
  Control.Exception.catch (liftM Ok ioaction)
89 1251817b Iustin Pop
    (\e -> return . Bad . show $ (e::IOException))
90 d575c755 Iustin Pop
91 9188aeef Iustin Pop
-- | Append the default port if not passed in.
92 e015b554 Iustin Pop
formatHost :: String -> String
93 e015b554 Iustin Pop
formatHost master =
94 ebf38064 Iustin Pop
  if ':' `elem` master
95 ebf38064 Iustin Pop
    then  master
96 a69ff623 Iustin Pop
    else "https://" ++ master ++ ":" ++ show C.defaultRapiPort
97 e015b554 Iustin Pop
98 9188aeef Iustin Pop
-- | Parse a instance list in JSON format.
99 040afc35 Iustin Pop
getInstances :: NameAssoc
100 040afc35 Iustin Pop
             -> String
101 040afc35 Iustin Pop
             -> Result [(String, Instance.Instance)]
102 262f3e6c Iustin Pop
getInstances ktn body =
103 ebf38064 Iustin Pop
  loadJSArray "Parsing instance data" body >>=
104 ebf38064 Iustin Pop
  mapM (parseInstance ktn . fromJSObject)
105 a7654563 Iustin Pop
106 9188aeef Iustin Pop
-- | Parse a node list in JSON format.
107 10ef6b4e Iustin Pop
getNodes :: NameAssoc -> String -> Result [(String, Node.Node)]
108 10ef6b4e Iustin Pop
getNodes ktg body = loadJSArray "Parsing node data" body >>=
109 ebf38064 Iustin Pop
                    mapM (parseNode ktg . fromJSObject)
110 a7654563 Iustin Pop
111 a679e9dc Iustin Pop
-- | Parse a group list in JSON format.
112 a679e9dc Iustin Pop
getGroups :: String -> Result [(String, Group.Group)]
113 a679e9dc Iustin Pop
getGroups body = loadJSArray "Parsing group data" body >>=
114 ebf38064 Iustin Pop
                 mapM (parseGroup . fromJSObject)
115 a679e9dc Iustin Pop
116 9188aeef Iustin Pop
-- | Construct an instance from a JSON object.
117 6ff78049 Iustin Pop
parseInstance :: NameAssoc
118 28f19313 Iustin Pop
              -> JSRecord
119 040afc35 Iustin Pop
              -> Result (String, Instance.Instance)
120 040afc35 Iustin Pop
parseInstance ktn a = do
121 117dc2d8 Iustin Pop
  name <- tryFromObj "Parsing new instance" a "name"
122 6a062ff9 Iustin Pop
  let owner_name = "Instance '" ++ name ++ "', error while parsing data"
123 6402a260 Iustin Pop
  let extract s x = tryFromObj owner_name x s
124 117dc2d8 Iustin Pop
  disk <- extract "disk_usage" a
125 2724417c Bernardo Dal Seno
  dsizes <- extract "disk.sizes" a
126 2724417c Bernardo Dal Seno
  dspindles <- tryArrayMaybeFromObj owner_name a "disk.spindles"
127 117dc2d8 Iustin Pop
  beparams <- liftM fromJSObject (extract "beparams" a)
128 6402a260 Iustin Pop
  omem <- extract "oper_ram" a
129 3603605a Iustin Pop
  mem <- case omem of
130 3603605a Iustin Pop
           JSRational _ _ -> annotateResult owner_name (fromJVal omem)
131 135f64a3 Iustin Pop
           _ -> extract "memory" beparams `mplus` extract "maxmem" beparams
132 117dc2d8 Iustin Pop
  vcpus <- extract "vcpus" beparams
133 117dc2d8 Iustin Pop
  pnode <- extract "pnode" a >>= lookupNode ktn name
134 117dc2d8 Iustin Pop
  snodes <- extract "snodes" a
135 72747d91 Iustin Pop
  snode <- case snodes of
136 72747d91 Iustin Pop
             [] -> return Node.noSecondary
137 72747d91 Iustin Pop
             x:_ -> readEitherString x >>= lookupNode ktn name
138 117dc2d8 Iustin Pop
  running <- extract "status" a
139 17e7af2b Iustin Pop
  tags <- extract "tags" a
140 a041ebb5 Iustin Pop
  auto_balance <- extract "auto_balance" beparams
141 5a4a3b7f Iustin Pop
  dt <- extract "disk_template" a
142 ec629280 René Nussbaumer
  su <- extract "spindle_use" beparams
143 2724417c Bernardo Dal Seno
  let disks = zipWith Instance.Disk dsizes dspindles
144 241cea1e Klaus Aehlig
  let inst = Instance.create name mem disk disks vcpus running tags
145 908c2f67 Thomas Thrainer
             auto_balance pnode snode dt su []
146 040afc35 Iustin Pop
  return (name, inst)
147 a7654563 Iustin Pop
148 9188aeef Iustin Pop
-- | Construct a node from a JSON object.
149 28f19313 Iustin Pop
parseNode :: NameAssoc -> JSRecord -> Result (String, Node.Node)
150 10ef6b4e Iustin Pop
parseNode ktg a = do
151 117dc2d8 Iustin Pop
  name <- tryFromObj "Parsing new node" a "name"
152 6a062ff9 Iustin Pop
  let desc = "Node '" ++ name ++ "', error while parsing data"
153 3daaab6c Helga Velroyen
      extract key = tryFromObj desc a key
154 3daaab6c Helga Velroyen
      extractDef def key = fromObjWithDefault a key def
155 117dc2d8 Iustin Pop
  offline <- extract "offline"
156 b45222ce Iustin Pop
  drained <- extract "drained"
157 3986684e Iustin Pop
  vm_cap  <- annotateResult desc $ maybeFromObj a "vm_capable"
158 3986684e Iustin Pop
  let vm_cap' = fromMaybe True vm_cap
159 8bc34c7b Iustin Pop
  ndparams <- extract "ndparams" >>= asJSObject
160 c324da14 Bernardo Dal Seno
  excl_stor <- tryFromObj desc (fromJSObject ndparams) "exclusive_storage"
161 3986684e Iustin Pop
  guuid   <- annotateResult desc $ maybeFromObj a "group.uuid"
162 3986684e Iustin Pop
  guuid' <-  lookupGroup ktg name (fromMaybe defaultGroupID guuid)
163 67ec18c0 Jose A. Lopes
  let live = not offline && vm_cap'
164 8c72f711 Bernardo Dal Seno
      lvextract def = eitherLive live def . extract
165 3daaab6c Helga Velroyen
      lvextractDef def = eitherLive live def . extractDef def
166 96f9b0a6 Bernardo Dal Seno
  sptotal <- if excl_stor
167 96f9b0a6 Bernardo Dal Seno
             then lvextract 0 "sptotal"
168 96f9b0a6 Bernardo Dal Seno
             else tryFromObj desc (fromJSObject ndparams) "spindle_count"
169 3daaab6c Helga Velroyen
  spfree <- lvextractDef 0 "spfree"
170 8c72f711 Bernardo Dal Seno
  mtotal <- lvextract 0.0 "mtotal"
171 8c72f711 Bernardo Dal Seno
  mnode <- lvextract 0 "mnode"
172 8c72f711 Bernardo Dal Seno
  mfree <- lvextract 0 "mfree"
173 3daaab6c Helga Velroyen
  dtotal <- lvextractDef 0.0 "dtotal"
174 3daaab6c Helga Velroyen
  dfree <- lvextractDef 0 "dfree"
175 8c72f711 Bernardo Dal Seno
  ctotal <- lvextract 0.0 "ctotal"
176 c8c071cb Bernardo Dal Seno
  cnos <- lvextract 0 "cnos"
177 8c72f711 Bernardo Dal Seno
  tags <- extract "tags"
178 8c72f711 Bernardo Dal Seno
  let node = flip Node.setNodeTags tags $
179 c8c071cb Bernardo Dal Seno
             Node.create name mtotal mnode mfree dtotal dfree ctotal cnos
180 affe1792 Klaus Aehlig
             (not live || drained) sptotal spfree guuid' excl_stor
181 262f3e6c Iustin Pop
  return (name, node)
182 00b15752 Iustin Pop
183 a679e9dc Iustin Pop
-- | Construct a group from a JSON object.
184 28f19313 Iustin Pop
parseGroup :: JSRecord -> Result (String, Group.Group)
185 a679e9dc Iustin Pop
parseGroup a = do
186 a679e9dc Iustin Pop
  name <- tryFromObj "Parsing new group" a "name"
187 a679e9dc Iustin Pop
  let extract s = tryFromObj ("Group '" ++ name ++ "'") a s
188 a679e9dc Iustin Pop
  uuid <- extract "uuid"
189 2ddabf4f Iustin Pop
  apol <- extract "alloc_policy"
190 6cff91f5 Iustin Pop
  ipol <- extract "ipolicy"
191 6b6e335b Dato Simó
  tags <- extract "tags"
192 c8b199db Thomas Thrainer
  -- TODO: parse networks to which this group is connected
193 c8b199db Thomas Thrainer
  return (uuid, Group.create name uuid apol [] ipol tags)
194 a679e9dc Iustin Pop
195 df5d5433 Iustin Pop
-- | Parse cluster data from the info resource.
196 bf028b21 Klaus Aehlig
parseCluster :: JSObject JSValue -> Result ([String], IPolicy, String)
197 df5d5433 Iustin Pop
parseCluster obj = do
198 df5d5433 Iustin Pop
  let obj' = fromJSObject obj
199 df5d5433 Iustin Pop
      extract s = tryFromObj "Parsing cluster data" obj' s
200 bf028b21 Klaus Aehlig
  master <- extract "master"
201 df5d5433 Iustin Pop
  tags <- extract "tags"
202 df5d5433 Iustin Pop
  ipolicy <- extract "ipolicy"
203 bf028b21 Klaus Aehlig
  return (tags, ipolicy, master)
204 df5d5433 Iustin Pop
205 748bfcc2 Iustin Pop
-- | Loads the raw cluster data from an URL.
206 d575c755 Iustin Pop
readDataHttp :: String -- ^ Cluster or URL to use as source
207 d575c755 Iustin Pop
             -> IO (Result String, Result String, Result String, Result String)
208 d575c755 Iustin Pop
readDataHttp master = do
209 040afc35 Iustin Pop
  let url = formatHost master
210 a679e9dc Iustin Pop
  group_body <- getUrl $ printf "%s/2/groups?bulk=1" url
211 040afc35 Iustin Pop
  node_body <- getUrl $ printf "%s/2/nodes?bulk=1" url
212 040afc35 Iustin Pop
  inst_body <- getUrl $ printf "%s/2/instances?bulk=1" url
213 df5d5433 Iustin Pop
  info_body <- getUrl $ printf "%s/2/info" url
214 df5d5433 Iustin Pop
  return (group_body, node_body, inst_body, info_body)
215 748bfcc2 Iustin Pop
216 d575c755 Iustin Pop
-- | Loads the raw cluster data from the filesystem.
217 d575c755 Iustin Pop
readDataFile:: String -- ^ Path to the directory containing the files
218 d575c755 Iustin Pop
             -> IO (Result String, Result String, Result String, Result String)
219 d575c755 Iustin Pop
readDataFile path = do
220 2cdaf225 Iustin Pop
  group_body <- ioErrToResult . readFile $ path </> "groups.json"
221 2cdaf225 Iustin Pop
  node_body <- ioErrToResult . readFile $ path </> "nodes.json"
222 2cdaf225 Iustin Pop
  inst_body <- ioErrToResult . readFile $ path </> "instances.json"
223 2cdaf225 Iustin Pop
  info_body <- ioErrToResult . readFile $ path </> "info.json"
224 d575c755 Iustin Pop
  return (group_body, node_body, inst_body, info_body)
225 d575c755 Iustin Pop
226 d575c755 Iustin Pop
-- | Loads data via either 'readDataFile' or 'readDataHttp'.
227 d575c755 Iustin Pop
readData :: String -- ^ URL to use as source
228 d575c755 Iustin Pop
         -> IO (Result String, Result String, Result String, Result String)
229 5b11f8db Iustin Pop
readData url =
230 d575c755 Iustin Pop
  if filePrefix `isPrefixOf` url
231 d575c755 Iustin Pop
    then readDataFile (drop (length filePrefix) url)
232 d575c755 Iustin Pop
    else readDataHttp url
233 d575c755 Iustin Pop
234 525bfb36 Iustin Pop
-- | Builds the cluster data from the raw Rapi content.
235 a679e9dc Iustin Pop
parseData :: (Result String, Result String, Result String, Result String)
236 f4f6eb0b Iustin Pop
          -> Result ClusterData
237 df5d5433 Iustin Pop
parseData (group_body, node_body, inst_body, info_body) = do
238 3667467d Iustin Pop
  group_data <- group_body >>= getGroups
239 10ef6b4e Iustin Pop
  let (group_names, group_idx) = assignIndices group_data
240 10ef6b4e Iustin Pop
  node_data <- node_body >>= getNodes group_names
241 748bfcc2 Iustin Pop
  let (node_names, node_idx) = assignIndices node_data
242 748bfcc2 Iustin Pop
  inst_data <- inst_body >>= getInstances node_names
243 748bfcc2 Iustin Pop
  let (_, inst_idx) = assignIndices inst_data
244 908c2f67 Thomas Thrainer
  (tags, ipolicy, master) <-
245 bf028b21 Klaus Aehlig
    info_body >>=
246 bf028b21 Klaus Aehlig
    (fromJResult "Parsing cluster info" . decodeStrict) >>=
247 bf028b21 Klaus Aehlig
    parseCluster
248 bf028b21 Klaus Aehlig
  node_idx' <- setMaster node_names node_idx master
249 bf028b21 Klaus Aehlig
  return (ClusterData group_idx node_idx' inst_idx tags ipolicy)
250 748bfcc2 Iustin Pop
251 525bfb36 Iustin Pop
-- | Top level function for data loading.
252 748bfcc2 Iustin Pop
loadData :: String -- ^ Cluster or URL to use as source
253 f4f6eb0b Iustin Pop
         -> IO (Result ClusterData)
254 2a8e2dc9 Iustin Pop
loadData = fmap parseData . readData