Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Confd / Server.hs @ d24fc4b6

History | View | Annotate | Download (10.9 kB)

1
{-| Implementation of the Ganeti confd server functionality.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2011, 2012, 2013 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.Confd.Server
27
  ( main
28
  , checkMain
29
  , prepMain
30
  ) where
31

    
32
import Control.Concurrent
33
import Control.Monad (forever, liftM)
34
import Data.IORef
35
import Data.List
36
import qualified Data.Map as M
37
import Data.Maybe (fromMaybe)
38
import qualified Network.Socket as S
39
import System.Exit
40
import System.IO
41
import qualified Text.JSON as J
42

    
43
import Ganeti.BasicTypes
44
import Ganeti.Errors
45
import Ganeti.Daemon
46
import Ganeti.JSON
47
import Ganeti.Objects
48
import Ganeti.Confd.Types
49
import Ganeti.Confd.Utils
50
import Ganeti.Config
51
import Ganeti.ConfigReader
52
import Ganeti.Hash
53
import Ganeti.Logging
54
import qualified Ganeti.Constants as C
55
import qualified Ganeti.Query.Cluster as QCluster
56
import Ganeti.Utils
57

    
58
-- * Types and constants definitions
59

    
60
-- | What we store as configuration.
61
type CRef = IORef (Result (ConfigData, LinkIpMap))
62

    
63
-- | A small type alias for readability.
64
type StatusAnswer = (ConfdReplyStatus, J.JSValue)
65

    
66
-- | Unknown entry standard response.
67
queryUnknownEntry :: StatusAnswer
68
queryUnknownEntry = (ReplyStatusError, J.showJSON ConfdErrorUnknownEntry)
69

    
70
{- not used yet
71
-- | Internal error standard response.
72
queryInternalError :: StatusAnswer
73
queryInternalError = (ReplyStatusError, J.showJSON ConfdErrorInternal)
74
-}
75

    
76
-- | Argument error standard response.
77
queryArgumentError :: StatusAnswer
78
queryArgumentError = (ReplyStatusError, J.showJSON ConfdErrorArgument)
79

    
80
-- | Converter from specific error to a string format.
81
gntErrorToResult :: ErrorResult a -> Result a
82
gntErrorToResult (Bad err) = Bad (show err)
83
gntErrorToResult (Ok x) = Ok x
84

    
85
-- * Confd base functionality
86

    
87
-- | Computes the node role.
88
nodeRole :: ConfigData -> String -> Result ConfdNodeRole
89
nodeRole cfg name = do
90
  cmaster <- errToResult $ QCluster.clusterMasterNodeName cfg
91
  mnode <- errToResult $ getNode cfg name
92
  let role = case mnode of
93
               node | cmaster == name -> NodeRoleMaster
94
                    | nodeDrained node -> NodeRoleDrained
95
                    | nodeOffline node -> NodeRoleOffline
96
                    | nodeMasterCandidate node -> NodeRoleCandidate
97
               _ -> NodeRoleRegular
98
  return role
99

    
100
-- | Does an instance ip -> instance -> primary node -> primary ip
101
-- transformation.
102
getNodePipByInstanceIp :: ConfigData
103
                       -> LinkIpMap
104
                       -> String
105
                       -> String
106
                       -> StatusAnswer
107
getNodePipByInstanceIp cfg linkipmap link instip =
108
  case M.lookup instip (M.findWithDefault M.empty link linkipmap) of
109
    Nothing -> queryUnknownEntry
110
    Just instname ->
111
      case getInstPrimaryNode cfg instname of
112
        Bad _ -> queryUnknownEntry -- either instance or node not found
113
        Ok node -> (ReplyStatusOk, J.showJSON (nodePrimaryIp node))
114

    
115
-- | Builds the response to a given query.
116
buildResponse :: (ConfigData, LinkIpMap) -> ConfdRequest -> Result StatusAnswer
117
buildResponse (cfg, _) (ConfdRequest { confdRqType = ReqPing }) =
118
  return (ReplyStatusOk, J.showJSON (configVersion cfg))
119

    
120
buildResponse cdata req@(ConfdRequest { confdRqType = ReqClusterMaster }) =
121
  case confdRqQuery req of
122
    EmptyQuery -> liftM ((,) ReplyStatusOk . J.showJSON) master_name
123
    PlainQuery _ -> return queryArgumentError
124
    DictQuery reqq -> do
125
      mnode <- gntErrorToResult $ getNode cfg master_uuid
126
      mname <- master_name
127
      let fvals = map (\field -> case field of
128
                                   ReqFieldName -> mname
129
                                   ReqFieldIp -> clusterMasterIp cluster
130
                                   ReqFieldMNodePip -> nodePrimaryIp mnode
131
                      ) (confdReqQFields reqq)
132
      return (ReplyStatusOk, J.showJSON fvals)
133
    where master_uuid = clusterMasterNode cluster
134
          master_name = errToResult $ QCluster.clusterMasterNodeName cfg
135
          cluster = configCluster cfg
136
          cfg = fst cdata
137

    
138
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeRoleByName }) = do
139
  node_name <- case confdRqQuery req of
140
                 PlainQuery str -> return str
141
                 _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
142
  role <- nodeRole (fst cdata) node_name
143
  return (ReplyStatusOk, J.showJSON role)
144

    
145
buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipList }) =
146
  -- note: we use foldlWithKey because that's present accross more
147
  -- versions of the library
148
  return (ReplyStatusOk, J.showJSON $
149
          M.foldlWithKey (\accu _ n -> nodePrimaryIp n:accu) []
150
          (fromContainer . configNodes . fst $ cdata))
151

    
152
buildResponse cdata (ConfdRequest { confdRqType = ReqMcPipList }) =
153
  -- note: we use foldlWithKey because that's present accross more
154
  -- versions of the library
155
  return (ReplyStatusOk, J.showJSON $
156
          M.foldlWithKey (\accu _ n -> if nodeMasterCandidate n
157
                                         then nodePrimaryIp n:accu
158
                                         else accu) []
159
          (fromContainer . configNodes . fst $ cdata))
160

    
161
buildResponse (cfg, linkipmap)
162
              req@(ConfdRequest { confdRqType = ReqInstIpsList }) = do
163
  link <- case confdRqQuery req of
164
            PlainQuery str -> return str
165
            EmptyQuery -> return (getDefaultNicLink cfg)
166
            _ -> fail "Invalid query type"
167
  return (ReplyStatusOk, J.showJSON $ getInstancesIpByLink linkipmap link)
168

    
169
buildResponse cdata (ConfdRequest { confdRqType = ReqNodePipByInstPip
170
                                  , confdRqQuery = DictQuery query}) =
171
  let (cfg, linkipmap) = cdata
172
      link = fromMaybe (getDefaultNicLink cfg) (confdReqQLink query)
173
  in case confdReqQIp query of
174
       Just ip -> return $ getNodePipByInstanceIp cfg linkipmap link ip
175
       Nothing -> return (ReplyStatusOk,
176
                          J.showJSON $
177
                           map (getNodePipByInstanceIp cfg linkipmap link)
178
                           (confdReqQIpList query))
179

    
180
buildResponse _ (ConfdRequest { confdRqType = ReqNodePipByInstPip }) =
181
  return queryArgumentError
182

    
183
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeDrbd }) = do
184
  let cfg = fst cdata
185
  node_name <- case confdRqQuery req of
186
                 PlainQuery str -> return str
187
                 _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
188
  node <- gntErrorToResult $ getNode cfg node_name
189
  let minors = concatMap (getInstMinorsForNode (nodeName node)) .
190
               M.elems . fromContainer . configInstances $ cfg
191
      encoded = [J.JSArray [J.showJSON a, J.showJSON b, J.showJSON c,
192
                             J.showJSON d, J.showJSON e, J.showJSON f] |
193
                 (a, b, c, d, e, f) <- minors]
194
  return (ReplyStatusOk, J.showJSON encoded)
195

    
196
-- | Return the list of instances for a node (as ([primary], [secondary])) given
197
-- the node name.
198
buildResponse cdata req@(ConfdRequest { confdRqType = ReqNodeInstances }) = do
199
  let cfg = fst cdata
200
  node_name <- case confdRqQuery req of
201
                PlainQuery str -> return str
202
                _ -> fail $ "Invalid query type " ++ show (confdRqQuery req)
203
  node <-
204
    case getNode cfg node_name of
205
      Ok n -> return n
206
      Bad e -> fail $ "Node not found in the configuration: " ++ show e
207
  let node_uuid = nodeUuid node
208
      instances = getNodeInstances cfg node_uuid
209
  return (ReplyStatusOk, J.showJSON instances)
210

    
211
-- | Creates a ConfdReply from a given answer.
212
serializeResponse :: Result StatusAnswer -> ConfdReply
213
serializeResponse r =
214
    let (status, result) = case r of
215
                    Bad err -> (ReplyStatusError, J.showJSON err)
216
                    Ok (code, val) -> (code, val)
217
    in ConfdReply { confdReplyProtocol = 1
218
                  , confdReplyStatus   = status
219
                  , confdReplyAnswer   = result
220
                  , confdReplySerial   = 0 }
221

    
222
-- ** Client input/output handlers
223

    
224
-- | Main loop for a given client.
225
responder :: CRef -> S.Socket -> HashKey -> String -> S.SockAddr -> IO ()
226
responder cfgref socket hmac msg peer = do
227
  ctime <- getCurrentTime
228
  case parseRequest hmac msg ctime of
229
    Ok (origmsg, rq) -> do
230
              logDebug $ "Processing request: " ++ rStripSpace origmsg
231
              mcfg <- readIORef cfgref
232
              let response = respondInner mcfg hmac rq
233
              _ <- S.sendTo socket response peer
234
              logDebug $ "Response sent: " ++ response
235
              return ()
236
    Bad err -> logInfo $ "Failed to parse incoming message: " ++ err
237
  return ()
238

    
239
-- | Inner helper function for a given client. This generates the
240
-- final encoded message (as a string), ready to be sent out to the
241
-- client.
242
respondInner :: Result (ConfigData, LinkIpMap) -> HashKey
243
             -> ConfdRequest -> String
244
respondInner cfg hmac rq =
245
  let rsalt = confdRqRsalt rq
246
      innermsg = serializeResponse (cfg >>= flip buildResponse rq)
247
      innerserialised = J.encodeStrict innermsg
248
      outermsg = signMessage hmac rsalt innerserialised
249
      outerserialised = C.confdMagicFourcc ++ J.encodeStrict outermsg
250
  in outerserialised
251

    
252
-- | Main listener loop.
253
listener :: S.Socket -> HashKey
254
         -> (S.Socket -> HashKey -> String -> S.SockAddr -> IO ())
255
         -> IO ()
256
listener s hmac resp = do
257
  (msg, _, peer) <- S.recvFrom s 4096
258
  if C.confdMagicFourcc `isPrefixOf` msg
259
    then forkIO (resp s hmac (drop 4 msg) peer) >> return ()
260
    else logDebug "Invalid magic code!" >> return ()
261
  return ()
262

    
263
-- | Type alias for prepMain results
264
type PrepResult = (S.Socket, IORef (Result (ConfigData, LinkIpMap)))
265

    
266
-- | Check function for confd.
267
checkMain :: CheckFn (S.Family, S.SockAddr)
268
checkMain opts = do
269
  parseresult <- parseAddress opts C.defaultConfdPort
270
  case parseresult of
271
    Bad msg -> do
272
      hPutStrLn stderr $ "parsing bind address: " ++ msg
273
      return . Left $ ExitFailure 1
274
    Ok v -> return $ Right v
275

    
276
-- | Prepare function for confd.
277
prepMain :: PrepFn (S.Family, S.SockAddr) PrepResult
278
prepMain _ (af_family, bindaddr) = do
279
  s <- S.socket af_family S.Datagram S.defaultProtocol
280
  S.bindSocket s bindaddr
281
  cref <- newIORef (Bad "Configuration not yet loaded")
282
  return (s, cref)
283

    
284
-- | Main function.
285
main :: MainFn (S.Family, S.SockAddr) PrepResult
286
main _ _ (s, cref) = do
287
  let cfg_transform :: Result ConfigData -> Result (ConfigData, LinkIpMap)
288
      cfg_transform = liftM (\cfg -> (cfg, buildLinkIpInstnameMap cfg))
289
  initConfigReader cfg_transform cref
290

    
291
  hmac <- getClusterHmac
292
  -- enter the responder loop
293
  forever $ listener s hmac (responder cref)