Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / Rpc.hs @ c1c5aab1

History | View | Annotate | Download (7.8 kB)

1
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, CPP,
2
  BangPatterns, TemplateHaskell #-}
3

    
4
{-| Implementation of the RPC client.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2012 Google Inc.
11

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Ganeti.Rpc
30
  ( RpcCall
31
  , RpcResult
32
  , Rpc
33
  , RpcError(..)
34
  , executeRpcCall
35

    
36
  , rpcCallName
37
  , rpcCallTimeout
38
  , rpcCallData
39
  , rpcCallAcceptOffline
40

    
41
  , rpcResultFill
42

    
43
  , InstanceInfo(..)
44
  , RpcCallAllInstancesInfo(..)
45
  , RpcResultAllInstancesInfo(..)
46

    
47
  , RpcCallInstanceList(..)
48
  , RpcResultInstanceList(..)
49

    
50
  , rpcTimeoutFromRaw -- FIXME: Not used anywhere
51
  ) where
52

    
53
import qualified Text.JSON as J
54
import Text.JSON (makeObj)
55

    
56
#ifndef NO_CURL
57
import Network.Curl
58
#endif
59

    
60
import qualified Ganeti.Constants as C
61
import Ganeti.Objects
62
import Ganeti.THH
63
import Ganeti.HTools.Compat
64
import Ganeti.HTools.JSON
65

    
66
#ifndef NO_CURL
67
-- | The curl options used for RPC.
68
curlOpts :: [CurlOption]
69
curlOpts = [ CurlFollowLocation False
70
           , CurlCAInfo C.nodedCertFile
71
           , CurlSSLVerifyHost 0
72
           , CurlSSLVerifyPeer True
73
           , CurlSSLCertType "PEM"
74
           , CurlSSLCert C.nodedCertFile
75
           , CurlSSLKeyType "PEM"
76
           , CurlSSLKey C.nodedCertFile
77
           , CurlConnectTimeout (fromIntegral C.rpcConnectTimeout)
78
           ]
79
#endif
80

    
81
-- | Data type for RPC error reporting.
82
data RpcError
83
  = CurlDisabledError
84
  | CurlLayerError Node String
85
  | JsonDecodeError String
86
  | OfflineNodeError Node
87
  deriving Eq
88

    
89
instance Show RpcError where
90
  show CurlDisabledError =
91
    "RPC/curl backend disabled at compile time"
92
  show (CurlLayerError node code) =
93
    "Curl error for " ++ nodeName node ++ ", error " ++ code
94
  show (JsonDecodeError msg) =
95
    "Error while decoding JSON from HTTP response " ++ msg
96
  show (OfflineNodeError node) =
97
    "Node " ++ nodeName node ++ " is marked as offline"
98

    
99
rpcErrorJsonReport :: (Monad m) => J.Result a -> m (Either RpcError a)
100
rpcErrorJsonReport (J.Error x) = return $ Left $ JsonDecodeError x
101
rpcErrorJsonReport (J.Ok x) = return $ Right x
102

    
103
-- | Basic timeouts for RPC calls.
104
$(declareIADT "RpcTimeout"
105
  [ ( "Urgent",    'C.rpcTmoUrgent )
106
  , ( "Fast",      'C.rpcTmoFast )
107
  , ( "Normal",    'C.rpcTmoNormal )
108
  , ( "Slow",      'C.rpcTmoSlow )
109
  , ( "FourHours", 'C.rpcTmo4hrs )
110
  , ( "OneDay",    'C.rpcTmo1day )
111
  ])
112

    
113
-- | A generic class for RPC calls.
114
class (J.JSON a) => RpcCall a where
115
  -- | Give the (Python) name of the procedure.
116
  rpcCallName :: a -> String
117
  -- | Calculate the timeout value for the call execution.
118
  rpcCallTimeout :: a -> Int
119
  -- | Prepare arguments of the call to be send as POST.
120
  rpcCallData :: Node -> a -> String
121
  -- | Whether we accept offline nodes when making a call.
122
  rpcCallAcceptOffline :: a -> Bool
123

    
124
  rpcCallData _ = J.encode
125

    
126
-- | A generic class for RPC results with default implementation.
127
class (J.JSON a) => RpcResult a where
128
  -- | Create a result based on the received HTTP response.
129
  rpcResultFill :: (Monad m) => String -> m (Either RpcError a)
130

    
131
  rpcResultFill res = rpcErrorJsonReport $  J.decode res
132

    
133
-- | Generic class that ensures matching RPC call with its respective
134
-- result.
135
class (RpcCall a, RpcResult b) => Rpc a b | a -> b
136

    
137
-- | Http Request definition.
138
data HttpClientRequest = HttpClientRequest
139
  { requestTimeout :: Int
140
  , requestUrl :: String
141
  , requestPostData :: String
142
  }
143

    
144
-- | Execute the request and return the result as a plain String. When
145
-- curl reports an error, we propagate it.
146
executeHttpRequest :: Node -> Either RpcError HttpClientRequest
147
                   -> IO (Either RpcError String)
148

    
149
executeHttpRequest _ (Left rpc_err) = return $ Left rpc_err
150
#ifdef NO_CURL
151
executeHttpRequest _ _ = return $ Left CurlDisabledError
152
#else
153
executeHttpRequest node (Right request) = do
154
  let reqOpts = [ CurlTimeout (fromIntegral $ requestTimeout request)
155
                , CurlPostFields [requestPostData request]
156
                ]
157
      url = requestUrl request
158
  -- FIXME: This is very similar to getUrl in Htools/Rapi.hs
159
  (code, !body) <- curlGetString url $ curlOpts ++ reqOpts
160
  case code of
161
    CurlOK -> return $ Right body
162
    _ -> return $ Left $ CurlLayerError node (show code)
163
#endif
164

    
165
-- | Prepare url for the HTTP request.
166
prepareUrl :: (RpcCall a) => Node -> a -> String
167
prepareUrl node call =
168
  let node_ip = nodePrimaryIp node
169
      port = snd C.daemonsPortsGanetiNoded
170
      path_prefix = "https://" ++ (node_ip) ++ ":" ++ (show port) in
171
  path_prefix ++ "/" ++ rpcCallName call
172

    
173
-- | Create HTTP request for a given node provided it is online,
174
-- otherwise create empty response.
175
prepareHttpRequest ::  (RpcCall a) => Node -> a
176
                   -> Either RpcError HttpClientRequest
177
prepareHttpRequest node call
178
  | rpcCallAcceptOffline call ||
179
    (not $ nodeOffline node) =
180
      Right $ HttpClientRequest { requestTimeout = rpcCallTimeout call
181
                                , requestUrl = prepareUrl node call
182
                                , requestPostData = rpcCallData node call
183
                                }
184
  | otherwise = Left $ OfflineNodeError node
185

    
186
-- | Parse the response or propagate the error.
187
parseHttpResponse :: (Monad m, RpcResult a) => Either RpcError String
188
                  -> m (Either RpcError a)
189
parseHttpResponse (Left err) = return $ Left err
190
parseHttpResponse (Right response) = rpcResultFill response
191

    
192
-- | Execute RPC call for a sigle node.
193
executeSingleRpcCall :: (Rpc a b) => Node -> a -> IO (Node, Either RpcError b)
194
executeSingleRpcCall node call = do
195
  let request = prepareHttpRequest node call
196
  response <- executeHttpRequest node request
197
  result <- parseHttpResponse response
198
  return (node, result)
199

    
200
-- | Execute RPC call for many nodes in parallel.
201
executeRpcCall :: (Rpc a b) => [Node] -> a -> IO [(Node, Either RpcError b)]
202
executeRpcCall nodes call =
203
  sequence $ parMap rwhnf (uncurry executeSingleRpcCall)
204
               (zip nodes $ repeat call)
205

    
206
-- * RPC calls and results
207

    
208
-- | AllInstancesInfo
209
--   Returns information about all instances on the given nodes
210
$(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo" $
211
  [ simpleField "hypervisors" [t| [Hypervisor] |] ])
212

    
213
$(buildObject "InstanceInfo" "instInfo" $
214
  [ simpleField "name"   [t| String |]
215
  , simpleField "memory" [t| Int|]
216
  , simpleField "state"  [t| AdminState |]
217
  , simpleField "vcpus"  [t| Int |]
218
  , simpleField "time"   [t| Int |]
219
  ])
220

    
221
$(buildObject "RpcResultAllInstancesInfo" "rpcResAllInstInfo" $
222
  [ simpleField "instances" [t| [InstanceInfo] |] ])
223

    
224
instance RpcCall RpcCallAllInstancesInfo where
225
  rpcCallName _ = "all_instances_info"
226
  rpcCallTimeout _ = rpcTimeoutToRaw Urgent
227
  rpcCallAcceptOffline _ = False
228

    
229
instance RpcResult RpcResultAllInstancesInfo
230

    
231
instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo
232

    
233
-- | InstanceList
234
-- Returns the list of running instances on the given nodes.
235
$(buildObject "RpcCallInstanceList" "rpcCallInstList" $
236
  [ simpleField "hypervisors" [t| [Hypervisor] |] ])
237

    
238
$(buildObject "RpcResultInstanceList" "rpcResInstList" $
239
  [ simpleField "node"      [t| Node |]
240
  , simpleField "instances" [t| [String] |]
241
  ])
242

    
243
instance RpcCall RpcCallInstanceList where
244
  rpcCallName _ = "instance_list"
245
  rpcCallTimeout _ = rpcTimeoutToRaw Urgent
246
  rpcCallAcceptOffline _ = False
247

    
248
instance RpcResult RpcResultInstanceList
249

    
250
instance Rpc RpcCallInstanceList RpcResultInstanceList