Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Daemon.hs @ 5867e439

History | View | Annotate | Download (14.8 kB)

1
{-| Implementation of the generic daemon functionality.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2011, 2012 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.Daemon
27
  ( DaemonOptions(..)
28
  , OptType
29
  , CheckFn
30
  , PrepFn
31
  , MainFn
32
  , defaultOptions
33
  , oShowHelp
34
  , oShowVer
35
  , oNoDaemonize
36
  , oNoUserChecks
37
  , oDebug
38
  , oPort
39
  , oBindAddress
40
  , oSyslogUsage
41
  , parseArgs
42
  , parseAddress
43
  , cleanupSocket
44
  , describeError
45
  , genericMain
46
  ) where
47

    
48
import Control.Exception
49
import Control.Monad
50
import Data.Maybe (fromMaybe, listToMaybe)
51
import Data.Word
52
import GHC.IO.Handle (hDuplicateTo)
53
import Network.BSD (getHostName)
54
import qualified Network.Socket as Socket
55
import System.Console.GetOpt
56
import System.Exit
57
import System.Environment
58
import System.IO
59
import System.IO.Error (isDoesNotExistError, modifyIOError, annotateIOError)
60
import System.Posix.Directory
61
import System.Posix.Files
62
import System.Posix.IO
63
import System.Posix.Process
64
import System.Posix.Types
65
import System.Posix.Signals
66

    
67
import Ganeti.Common as Common
68
import Ganeti.Logging
69
import Ganeti.Runtime
70
import Ganeti.BasicTypes
71
import Ganeti.Utils
72
import qualified Ganeti.Constants as C
73
import qualified Ganeti.Ssconf as Ssconf
74

    
75
-- * Constants
76

    
77
-- | \/dev\/null path.
78
devNull :: FilePath
79
devNull = "/dev/null"
80

    
81
-- | Error message prefix, used in two separate paths (when forking
82
-- and when not).
83
daemonStartupErr :: String -> String
84
daemonStartupErr = ("Error when starting the daemon process: " ++)
85

    
86
-- * Data types
87

    
88
-- | Command line options structure.
89
data DaemonOptions = DaemonOptions
90
  { optShowHelp     :: Bool           -- ^ Just show the help
91
  , optShowVer      :: Bool           -- ^ Just show the program version
92
  , optShowComp     :: Bool           -- ^ Just show the completion info
93
  , optDaemonize    :: Bool           -- ^ Whether to daemonize or not
94
  , optPort         :: Maybe Word16   -- ^ Override for the network port
95
  , optDebug        :: Bool           -- ^ Enable debug messages
96
  , optNoUserChecks :: Bool           -- ^ Ignore user checks
97
  , optBindAddress  :: Maybe String   -- ^ Override for the bind address
98
  , optSyslogUsage  :: Maybe SyslogUsage -- ^ Override for Syslog usage
99
  }
100

    
101
-- | Default values for the command line options.
102
defaultOptions :: DaemonOptions
103
defaultOptions  = DaemonOptions
104
  { optShowHelp     = False
105
  , optShowVer      = False
106
  , optShowComp     = False
107
  , optDaemonize    = True
108
  , optPort         = Nothing
109
  , optDebug        = False
110
  , optNoUserChecks = False
111
  , optBindAddress  = Nothing
112
  , optSyslogUsage  = Nothing
113
  }
114

    
115
instance StandardOptions DaemonOptions where
116
  helpRequested = optShowHelp
117
  verRequested  = optShowVer
118
  compRequested = optShowComp
119
  requestHelp o = o { optShowHelp = True }
120
  requestVer  o = o { optShowVer  = True }
121
  requestComp o = o { optShowComp = True }
122

    
123
-- | Abrreviation for the option type.
124
type OptType = GenericOptType DaemonOptions
125

    
126
-- | Check function type.
127
type CheckFn a = DaemonOptions -> IO (Either ExitCode a)
128

    
129
-- | Prepare function type.
130
type PrepFn a b = DaemonOptions -> a -> IO b
131

    
132
-- | Main execution function type.
133
type MainFn a b = DaemonOptions -> a -> b -> IO ()
134

    
135
-- * Command line options
136

    
137
oNoDaemonize :: OptType
138
oNoDaemonize =
139
  (Option "f" ["foreground"]
140
   (NoArg (\ opts -> Ok opts { optDaemonize = False}))
141
   "Don't detach from the current terminal",
142
   OptComplNone)
143

    
144
oDebug :: OptType
145
oDebug =
146
  (Option "d" ["debug"]
147
   (NoArg (\ opts -> Ok opts { optDebug = True }))
148
   "Enable debug messages",
149
   OptComplNone)
150

    
151
oNoUserChecks :: OptType
152
oNoUserChecks =
153
  (Option "" ["no-user-checks"]
154
   (NoArg (\ opts -> Ok opts { optNoUserChecks = True }))
155
   "Ignore user checks",
156
   OptComplNone)
157

    
158
oPort :: Int -> OptType
159
oPort def =
160
  (Option "p" ["port"]
161
   (reqWithConversion (tryRead "reading port")
162
    (\port opts -> Ok opts { optPort = Just port }) "PORT")
163
   ("Network port (default: " ++ show def ++ ")"),
164
   OptComplInteger)
165

    
166
oBindAddress :: OptType
167
oBindAddress =
168
  (Option "b" ["bind"]
169
   (ReqArg (\addr opts -> Ok opts { optBindAddress = Just addr })
170
    "ADDR")
171
   "Bind address (default depends on cluster configuration)",
172
   OptComplInetAddr)
173

    
174
oSyslogUsage :: OptType
175
oSyslogUsage =
176
  (Option "" ["syslog"]
177
   (reqWithConversion syslogUsageFromRaw
178
    (\su opts -> Ok opts { optSyslogUsage = Just su })
179
    "SYSLOG")
180
   ("Enable logging to syslog (except debug \
181
    \messages); one of 'no', 'yes' or 'only' [" ++ C.syslogUsage ++
182
    "]"),
183
   OptComplChoices ["yes", "no", "only"])
184

    
185
-- | Generic options.
186
genericOpts :: [OptType]
187
genericOpts = [ oShowHelp
188
              , oShowVer
189
              , oShowComp
190
              ]
191

    
192
-- | Annotates and transforms IOErrors into a Result type. This can be
193
-- used in the error handler argument to 'catch', for example.
194
ioErrorToResult :: String -> IOError -> IO (Result a)
195
ioErrorToResult description exc =
196
  return . Bad $ description ++ ": " ++ show exc
197

    
198
-- | Small wrapper over getArgs and 'parseOpts'.
199
parseArgs :: String -> [OptType] -> IO (DaemonOptions, [String])
200
parseArgs cmd options = do
201
  cmd_args <- getArgs
202
  parseOpts defaultOptions cmd_args cmd (options ++ genericOpts) []
203

    
204
-- * Daemon-related functions
205

    
206
-- | PID file mode.
207
pidFileMode :: FileMode
208
pidFileMode = unionFileModes ownerReadMode ownerWriteMode
209

    
210
-- | PID file open flags.
211
pidFileFlags :: OpenFileFlags
212
pidFileFlags = defaultFileFlags { noctty = True, trunc = False }
213

    
214
-- | Writes a PID file and locks it.
215
writePidFile :: FilePath -> IO Fd
216
writePidFile path = do
217
  fd <- openFd path ReadWrite (Just pidFileMode) pidFileFlags
218
  setLock fd (WriteLock, AbsoluteSeek, 0, 0)
219
  my_pid <- getProcessID
220
  _ <- fdWrite fd (show my_pid ++ "\n")
221
  return fd
222

    
223
-- | Helper function to ensure a socket doesn't exist. Should only be
224
-- called once we have locked the pid file successfully.
225
cleanupSocket :: FilePath -> IO ()
226
cleanupSocket socketPath =
227
  catchJust (guard . isDoesNotExistError) (removeLink socketPath)
228
            (const $ return ())
229

    
230
-- | Sets up a daemon's environment.
231
setupDaemonEnv :: FilePath -> FileMode -> IO ()
232
setupDaemonEnv cwd umask = do
233
  changeWorkingDirectory cwd
234
  _ <- setFileCreationMask umask
235
  _ <- createSession
236
  return ()
237

    
238
-- | Signal handler for reopening log files.
239
handleSigHup :: FilePath -> IO ()
240
handleSigHup path = do
241
  setupDaemonFDs (Just path)
242
  logInfo "Reopening log files after receiving SIGHUP"
243

    
244
-- | Sets up a daemon's standard file descriptors.
245
setupDaemonFDs :: Maybe FilePath -> IO ()
246
setupDaemonFDs logfile = do
247
  null_in_handle <- openFile devNull ReadMode
248
  null_out_handle <- openFile (fromMaybe devNull logfile) AppendMode
249
  hDuplicateTo null_in_handle stdin
250
  hDuplicateTo null_out_handle stdout
251
  hDuplicateTo null_out_handle stderr
252
  hClose null_in_handle
253
  hClose null_out_handle
254

    
255
-- | Computes the default bind address for a given family.
256
defaultBindAddr :: Int                  -- ^ The port we want
257
                -> Socket.Family        -- ^ The cluster IP family
258
                -> Result (Socket.Family, Socket.SockAddr)
259
defaultBindAddr port Socket.AF_INET =
260
  Ok (Socket.AF_INET,
261
      Socket.SockAddrInet (fromIntegral port) Socket.iNADDR_ANY)
262
defaultBindAddr port Socket.AF_INET6 =
263
  Ok (Socket.AF_INET6,
264
      Socket.SockAddrInet6 (fromIntegral port) 0 Socket.iN6ADDR_ANY 0)
265
defaultBindAddr _ fam = Bad $ "Unsupported address family: " ++ show fam
266

    
267
-- | Based on the options, compute the socket address to use for the
268
-- daemon.
269
parseAddress :: DaemonOptions      -- ^ Command line options
270
             -> Int                -- ^ Default port for this daemon
271
             -> IO (Result (Socket.Family, Socket.SockAddr))
272
parseAddress opts defport = do
273
  let port = maybe defport fromIntegral $ optPort opts
274
  def_family <- Ssconf.getPrimaryIPFamily Nothing
275
  case optBindAddress opts of
276
    Nothing -> return (def_family >>= defaultBindAddr port)
277
    Just saddr -> Control.Exception.catch
278
                    (resolveAddr port saddr)
279
                    (ioErrorToResult $ "Invalid address " ++ saddr)
280

    
281
-- | Environment variable to override the assumed host name of the
282
-- current node.
283
vClusterHostNameEnvVar :: String
284
vClusterHostNameEnvVar = "GANETI_HOSTNAME"
285

    
286
getFQDN :: IO String
287
getFQDN = do
288
  hostname <- getHostName
289
  addrInfos <- Socket.getAddrInfo Nothing (Just hostname) Nothing
290
  let address = listToMaybe addrInfos >>= (Just . Socket.addrAddress)
291
  case address of
292
    Just a -> do
293
      fqdn <- liftM fst $ Socket.getNameInfo [] True False a
294
      return (fromMaybe hostname fqdn)
295
    Nothing -> return hostname
296

    
297
-- | Returns if the current node is the master node.
298
isMaster :: IO Bool
299
isMaster = do
300
  let ioErrorToNothing :: IOError -> IO (Maybe String)
301
      ioErrorToNothing _ = return Nothing
302
  vcluster_node <- Control.Exception.catch
303
                     (liftM Just (getEnv vClusterHostNameEnvVar))
304
                     ioErrorToNothing
305
  curNode <- case vcluster_node of
306
    Just node_name -> return node_name
307
    Nothing -> getFQDN
308
  masterNode <- Ssconf.getMasterNode Nothing
309
  case masterNode of
310
    Ok n -> return (curNode == n)
311
    Bad _ -> return False
312

    
313
-- | Ensures that the daemon runs on the right node (and exits
314
-- gracefully if it doesnt)
315
ensureNode :: GanetiDaemon -> IO ()
316
ensureNode daemon = do
317
  is_master <- isMaster
318
  when (daemonOnlyOnMaster daemon && not is_master) $ do
319
    putStrLn "Not master, exiting."
320
    exitWith (ExitFailure C.exitNotmaster)
321

    
322
-- | Run an I\/O action that might throw an I\/O error, under a
323
-- handler that will simply annotate and re-throw the exception.
324
describeError :: String -> Maybe Handle -> Maybe FilePath -> IO a -> IO a
325
describeError descr hndl fpath =
326
  modifyIOError (\e -> annotateIOError e descr hndl fpath)
327

    
328
-- | Run an I\/O action as a daemon.
329
--
330
-- WARNING: this only works in single-threaded mode (either using the
331
-- single-threaded runtime, or using the multi-threaded one but with
332
-- only one OS thread, i.e. -N1).
333
daemonize :: FilePath -> (Maybe Fd -> IO ()) -> IO ()
334
daemonize logfile action = do
335
  (rpipe, wpipe) <- createPipe
336
  -- first fork
337
  _ <- forkProcess $ do
338
    -- in the child
339
    closeFd rpipe
340
    let wpipe' = Just wpipe
341
    setupDaemonEnv "/" (unionFileModes groupModes otherModes)
342
    setupDaemonFDs (Just logfile) `Control.Exception.catch`
343
      handlePrepErr False wpipe'
344
    _ <- installHandler lostConnection (Catch (handleSigHup logfile)) Nothing
345
    -- second fork, launches the actual child code; standard
346
    -- double-fork technique
347
    _ <- forkProcess (action wpipe')
348
    exitImmediately ExitSuccess
349
  closeFd wpipe
350
  hndl <- fdToHandle rpipe
351
  errors <- hGetContents hndl
352
  ecode <- if null errors
353
             then return ExitSuccess
354
             else do
355
               hPutStrLn stderr $ daemonStartupErr errors
356
               return $ ExitFailure C.exitFailure
357
  exitImmediately ecode
358

    
359
-- | Generic daemon startup.
360
genericMain :: GanetiDaemon -- ^ The daemon we're running
361
            -> [OptType]    -- ^ The available options
362
            -> CheckFn a    -- ^ Check function
363
            -> PrepFn  a b  -- ^ Prepare function
364
            -> MainFn  a b  -- ^ Execution function
365
            -> IO ()
366
genericMain daemon options check_fn prep_fn exec_fn = do
367
  let progname = daemonName daemon
368

    
369
  (opts, args) <- parseArgs progname options
370

    
371
  ensureNode daemon
372

    
373
  exitUnless (null args) "This program doesn't take any arguments"
374

    
375
  unless (optNoUserChecks opts) $ do
376
    runtimeEnts <- getEnts
377
    ents <- exitIfBad "Can't find required user/groups" runtimeEnts
378
    verifyDaemonUser daemon ents
379

    
380
  syslog <- case optSyslogUsage opts of
381
              Nothing -> exitIfBad "Invalid cluster syslog setting" $
382
                         syslogUsageFromRaw C.syslogUsage
383
              Just v -> return v
384

    
385
  log_file <- daemonLogFile daemon
386
  -- run the check function and optionally exit if it returns an exit code
387
  check_result <- check_fn opts
388
  check_result' <- case check_result of
389
                     Left code -> exitWith code
390
                     Right v -> return v
391

    
392
  let processFn = if optDaemonize opts
393
                    then daemonize log_file
394
                    else \action -> action Nothing
395
  processFn $ innerMain daemon opts syslog check_result' prep_fn exec_fn
396

    
397
-- | Full prepare function.
398
--
399
-- This is executed after daemonization, and sets up both the log
400
-- files (a generic functionality) and the custom prepare function of
401
-- the daemon.
402
fullPrep :: GanetiDaemon  -- ^ The daemon we're running
403
         -> DaemonOptions -- ^ The options structure, filled from the cmdline
404
         -> SyslogUsage   -- ^ Syslog mode
405
         -> a             -- ^ Check results
406
         -> PrepFn a b    -- ^ Prepare function
407
         -> IO b
408
fullPrep daemon opts syslog check_result prep_fn = do
409
  logfile <- if optDaemonize opts
410
               then return Nothing
411
               else liftM Just $ daemonLogFile daemon
412
  pidfile <- daemonPidFile daemon
413
  let dname = daemonName daemon
414
  setupLogging logfile dname (optDebug opts) True False syslog
415
  _ <- describeError "writing PID file; already locked?"
416
         Nothing (Just pidfile) $ writePidFile pidfile
417
  logNotice $ dname ++ " daemon startup"
418
  prep_fn opts check_result
419

    
420
-- | Inner daemon function.
421
--
422
-- This is executed after daemonization.
423
innerMain :: GanetiDaemon  -- ^ The daemon we're running
424
          -> DaemonOptions -- ^ The options structure, filled from the cmdline
425
          -> SyslogUsage   -- ^ Syslog mode
426
          -> a             -- ^ Check results
427
          -> PrepFn a b    -- ^ Prepare function
428
          -> MainFn a b    -- ^ Execution function
429
          -> Maybe Fd      -- ^ Error reporting function
430
          -> IO ()
431
innerMain daemon opts syslog check_result prep_fn exec_fn fd = do
432
  prep_result <- fullPrep daemon opts syslog check_result prep_fn
433
                 `Control.Exception.catch` handlePrepErr True fd
434
  -- no error reported, we should now close the fd
435
  maybeCloseFd fd
436
  exec_fn opts check_result prep_result
437

    
438
-- | Daemon prepare error handling function.
439
handlePrepErr :: Bool -> Maybe Fd -> IOError -> IO a
440
handlePrepErr logging_setup fd err = do
441
  let msg = show err
442
  case fd of
443
    -- explicitly writing to the fd directly, since when forking it's
444
    -- better (safer) than trying to convert this into a full handle
445
    Just fd' -> fdWrite fd' msg >> return ()
446
    Nothing  -> hPutStrLn stderr (daemonStartupErr msg)
447
  when logging_setup $ logError msg
448
  exitWith $ ExitFailure 1
449

    
450
-- | Close a file descriptor.
451
maybeCloseFd :: Maybe Fd -> IO ()
452
maybeCloseFd Nothing   = return ()
453
maybeCloseFd (Just fd) = closeFd fd