options.py 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972
  1. import ConfigParser
  2. import socket
  3. import getopt
  4. import os
  5. import sys
  6. import tempfile
  7. import errno
  8. import signal
  9. import re
  10. import xmlrpclib
  11. import pwd
  12. import grp
  13. import resource
  14. import stat
  15. import pkg_resources
  16. import select
  17. import glob
  18. import platform
  19. import warnings
  20. from fcntl import fcntl
  21. from fcntl import F_SETFL, F_GETFL
  22. from supervisor.medusa import asyncore_25 as asyncore
  23. from supervisor.datatypes import boolean
  24. from supervisor.datatypes import integer
  25. from supervisor.datatypes import name_to_uid
  26. from supervisor.datatypes import gid_for_uid
  27. from supervisor.datatypes import existing_dirpath
  28. from supervisor.datatypes import byte_size
  29. from supervisor.datatypes import signal_number
  30. from supervisor.datatypes import list_of_exitcodes
  31. from supervisor.datatypes import dict_of_key_value_pairs
  32. from supervisor.datatypes import logfile_name
  33. from supervisor.datatypes import list_of_strings
  34. from supervisor.datatypes import octal_type
  35. from supervisor.datatypes import existing_directory
  36. from supervisor.datatypes import logging_level
  37. from supervisor.datatypes import colon_separated_user_group
  38. from supervisor.datatypes import inet_address
  39. from supervisor.datatypes import InetStreamSocketConfig
  40. from supervisor.datatypes import UnixStreamSocketConfig
  41. from supervisor.datatypes import url
  42. from supervisor.datatypes import Automatic
  43. from supervisor.datatypes import auto_restart
  44. from supervisor.datatypes import profile_options
  45. from supervisor.datatypes import set_here
  46. from supervisor import loggers
  47. from supervisor import states
  48. from supervisor import xmlrpc
  49. mydir = os.path.abspath(os.path.dirname(__file__))
  50. version_txt = os.path.join(mydir, 'version.txt')
  51. VERSION = open(version_txt).read().strip()
  52. def normalize_path(v):
  53. return os.path.normpath(os.path.abspath(os.path.expanduser(v)))
  54. class Dummy:
  55. pass
  56. class Options:
  57. stderr = sys.stderr
  58. stdout = sys.stdout
  59. exit = sys.exit
  60. warnings = warnings
  61. uid = gid = None
  62. progname = sys.argv[0]
  63. configfile = None
  64. schemadir = None
  65. configroot = None
  66. here = None
  67. # Class variable deciding whether positional arguments are allowed.
  68. # If you want positional arguments, set this to 1 in your subclass.
  69. positional_args_allowed = 0
  70. def __init__(self):
  71. self.names_list = []
  72. self.short_options = []
  73. self.long_options = []
  74. self.options_map = {}
  75. self.default_map = {}
  76. self.required_map = {}
  77. self.environ_map = {}
  78. self.attr_priorities = {}
  79. self.add(None, None, "h", "help", self.help)
  80. self.add("configfile", None, "c:", "configuration=")
  81. def default_configfile(self):
  82. """Return the name of the found config file or raise. """
  83. here = os.path.dirname(os.path.dirname(sys.argv[0]))
  84. paths = [os.path.join(here, 'etc', 'supervisord.conf'),
  85. os.path.join(here, 'supervisord.conf'),
  86. 'supervisord.conf', 'etc/supervisord.conf',
  87. '/etc/supervisord.conf']
  88. config = None
  89. for path in paths:
  90. if os.path.exists(path):
  91. config = path
  92. break
  93. if config is None:
  94. self.usage('No config file found at default paths (%s); '
  95. 'use the -c option to specify a config file '
  96. 'at a different path' % ', '.join(paths))
  97. return config
  98. def help(self, dummy):
  99. """Print a long help message to stdout and exit(0).
  100. Occurrences of "%s" in are replaced by self.progname.
  101. """
  102. help = self.doc
  103. if help.find("%s") > 0:
  104. help = help.replace("%s", self.progname)
  105. print help,
  106. self.exit(0)
  107. def usage(self, msg):
  108. """Print a brief error message to stderr and exit(2)."""
  109. self.stderr.write("Error: %s\n" % str(msg))
  110. self.stderr.write("For help, use %s -h\n" % self.progname)
  111. self.exit(2)
  112. def add(self,
  113. name=None, # attribute name on self
  114. confname=None, # dotted config path name
  115. short=None, # short option name
  116. long=None, # long option name
  117. handler=None, # handler (defaults to string)
  118. default=None, # default value
  119. required=None, # message if not provided
  120. flag=None, # if not None, flag value
  121. env=None, # if not None, environment variable
  122. ):
  123. """Add information about a configuration option.
  124. This can take several forms:
  125. add(name, confname)
  126. Configuration option 'confname' maps to attribute 'name'
  127. add(name, None, short, long)
  128. Command line option '-short' or '--long' maps to 'name'
  129. add(None, None, short, long, handler)
  130. Command line option calls handler
  131. add(name, None, short, long, handler)
  132. Assign handler return value to attribute 'name'
  133. In addition, one of the following keyword arguments may be given:
  134. default=... -- if not None, the default value
  135. required=... -- if nonempty, an error message if no value provided
  136. flag=... -- if not None, flag value for command line option
  137. env=... -- if not None, name of environment variable that
  138. overrides the configuration file or default
  139. """
  140. if flag is not None:
  141. if handler is not None:
  142. raise ValueError, "use at most one of flag= and handler="
  143. if not long and not short:
  144. raise ValueError, "flag= requires a command line flag"
  145. if short and short.endswith(":"):
  146. raise ValueError, "flag= requires a command line flag"
  147. if long and long.endswith("="):
  148. raise ValueError, "flag= requires a command line flag"
  149. handler = lambda arg, flag=flag: flag
  150. if short and long:
  151. if short.endswith(":") != long.endswith("="):
  152. raise ValueError, "inconsistent short/long options: %r %r" % (
  153. short, long)
  154. if short:
  155. if short[0] == "-":
  156. raise ValueError, "short option should not start with '-'"
  157. key, rest = short[:1], short[1:]
  158. if rest not in ("", ":"):
  159. raise ValueError, "short option should be 'x' or 'x:'"
  160. key = "-" + key
  161. if self.options_map.has_key(key):
  162. raise ValueError, "duplicate short option key '%s'" % key
  163. self.options_map[key] = (name, handler)
  164. self.short_options.append(short)
  165. if long:
  166. if long[0] == "-":
  167. raise ValueError, "long option should not start with '-'"
  168. key = long
  169. if key[-1] == "=":
  170. key = key[:-1]
  171. key = "--" + key
  172. if self.options_map.has_key(key):
  173. raise ValueError, "duplicate long option key '%s'" % key
  174. self.options_map[key] = (name, handler)
  175. self.long_options.append(long)
  176. if env:
  177. self.environ_map[env] = (name, handler)
  178. if name:
  179. if not hasattr(self, name):
  180. setattr(self, name, None)
  181. self.names_list.append((name, confname))
  182. if default is not None:
  183. self.default_map[name] = default
  184. if required:
  185. self.required_map[name] = required
  186. def _set(self, attr, value, prio):
  187. current = self.attr_priorities.get(attr, -1)
  188. if prio >= current:
  189. setattr(self, attr, value)
  190. self.attr_priorities[attr] = prio
  191. def realize(self, args=None, doc=None,
  192. progname=None, raise_getopt_errs=True):
  193. """Realize a configuration.
  194. Optional arguments:
  195. args -- the command line arguments, less the program name
  196. (default is sys.argv[1:])
  197. doc -- usage message (default is __main__.__doc__)
  198. """
  199. # Provide dynamic default method arguments
  200. if args is None:
  201. args = sys.argv[1:]
  202. if progname is None:
  203. progname = sys.argv[0]
  204. if doc is None:
  205. import __main__
  206. doc = __main__.__doc__
  207. self.progname = progname
  208. self.doc = doc
  209. self.options = []
  210. self.args = []
  211. # Call getopt
  212. try:
  213. self.options, self.args = getopt.getopt(
  214. args, "".join(self.short_options), self.long_options)
  215. except getopt.error, msg:
  216. if raise_getopt_errs:
  217. self.usage(msg)
  218. # Check for positional args
  219. if self.args and not self.positional_args_allowed:
  220. self.usage("positional arguments are not supported")
  221. # Process options returned by getopt
  222. for opt, arg in self.options:
  223. name, handler = self.options_map[opt]
  224. if handler is not None:
  225. try:
  226. arg = handler(arg)
  227. except ValueError, msg:
  228. self.usage("invalid value for %s %r: %s" % (opt, arg, msg))
  229. if name and arg is not None:
  230. if getattr(self, name) is not None:
  231. self.usage("conflicting command line option %r" % opt)
  232. self._set(name, arg, 2)
  233. # Process environment variables
  234. for envvar in self.environ_map.keys():
  235. name, handler = self.environ_map[envvar]
  236. if os.environ.has_key(envvar):
  237. value = os.environ[envvar]
  238. if handler is not None:
  239. try:
  240. value = handler(value)
  241. except ValueError, msg:
  242. self.usage("invalid environment value for %s %r: %s"
  243. % (envvar, value, msg))
  244. if name and value is not None:
  245. self._set(name, value, 1)
  246. if self.configfile is None:
  247. if os.getuid() == 0 and self.progname.find("supervisord") > -1: # pragma: no cover
  248. self.warnings.warn(
  249. 'Supervisord is running as root and it is searching '
  250. 'for its configuration file in default locations '
  251. '(including its current working directory); you '
  252. 'probably want to specify a "-c" argument specifying an '
  253. 'absolute path to a configuration file for improved '
  254. 'security.'
  255. )
  256. self.configfile = self.default_configfile()
  257. self.process_config_file()
  258. def process_config_file(self, do_usage=True):
  259. """Process config file."""
  260. if not hasattr(self.configfile, 'read'):
  261. self.here = os.path.abspath(os.path.dirname(self.configfile))
  262. set_here(self.here)
  263. try:
  264. self.read_config(self.configfile)
  265. except ValueError, msg:
  266. if do_usage:
  267. # if this is not called from an RPC method, run usage and exit.
  268. self.usage(str(msg))
  269. else:
  270. # if this is called from an RPC method, raise an error
  271. raise ValueError(msg)
  272. # Copy config options to attributes of self. This only fills
  273. # in options that aren't already set from the command line.
  274. for name, confname in self.names_list:
  275. if confname:
  276. parts = confname.split(".")
  277. obj = self.configroot
  278. for part in parts:
  279. if obj is None:
  280. break
  281. # Here AttributeError is not a user error!
  282. obj = getattr(obj, part)
  283. self._set(name, obj, 0)
  284. # Process defaults
  285. for name, value in self.default_map.items():
  286. if getattr(self, name) is None:
  287. setattr(self, name, value)
  288. # Process required options
  289. for name, message in self.required_map.items():
  290. if getattr(self, name) is None:
  291. self.usage(message)
  292. def get_plugins(self, parser, factory_key, section_prefix):
  293. factories = []
  294. for section in parser.sections():
  295. if not section.startswith(section_prefix):
  296. continue
  297. name = section.split(':', 1)[1]
  298. factory_spec = parser.saneget(section, factory_key, None)
  299. if factory_spec is None:
  300. raise ValueError('section [%s] does not specify a %s' %
  301. (section, factory_key))
  302. try:
  303. factory = self.import_spec(factory_spec)
  304. except ImportError:
  305. raise ValueError('%s cannot be resolved within [%s]' % (
  306. factory_spec, section))
  307. items = parser.items(section)
  308. items.remove((factory_key, factory_spec))
  309. factories.append((name, factory, dict(items)))
  310. return factories
  311. def import_spec(self, spec):
  312. return pkg_resources.EntryPoint.parse("x="+spec).load(False)
  313. class ServerOptions(Options):
  314. user = None
  315. sockchown = None
  316. sockchmod = None
  317. logfile = None
  318. loglevel = None
  319. pidfile = None
  320. passwdfile = None
  321. nodaemon = None
  322. environment = None
  323. httpservers = ()
  324. unlink_socketfiles = True
  325. mood = states.SupervisorStates.RUNNING
  326. def __init__(self):
  327. Options.__init__(self)
  328. self.configroot = Dummy()
  329. self.configroot.supervisord = Dummy()
  330. self.add(None, None, "v", "version", self.version)
  331. self.add("nodaemon", "supervisord.nodaemon", "n", "nodaemon", flag=1,
  332. default=0)
  333. self.add("user", "supervisord.user", "u:", "user=")
  334. self.add("umask", "supervisord.umask", "m:", "umask=",
  335. octal_type, default='022')
  336. self.add("directory", "supervisord.directory", "d:", "directory=",
  337. existing_directory)
  338. self.add("logfile", "supervisord.logfile", "l:", "logfile=",
  339. existing_dirpath, default="supervisord.log")
  340. self.add("logfile_maxbytes", "supervisord.logfile_maxbytes",
  341. "y:", "logfile_maxbytes=", byte_size,
  342. default=50 * 1024 * 1024) # 50MB
  343. self.add("logfile_backups", "supervisord.logfile_backups",
  344. "z:", "logfile_backups=", integer, default=10)
  345. self.add("loglevel", "supervisord.loglevel", "e:", "loglevel=",
  346. logging_level, default="info")
  347. self.add("pidfile", "supervisord.pidfile", "j:", "pidfile=",
  348. existing_dirpath, default="supervisord.pid")
  349. self.add("identifier", "supervisord.identifier", "i:", "identifier=",
  350. str, default="supervisor")
  351. self.add("childlogdir", "supervisord.childlogdir", "q:", "childlogdir=",
  352. existing_directory, default=tempfile.gettempdir())
  353. self.add("minfds", "supervisord.minfds",
  354. "a:", "minfds=", int, default=1024)
  355. self.add("minprocs", "supervisord.minprocs",
  356. "", "minprocs=", int, default=200)
  357. self.add("nocleanup", "supervisord.nocleanup",
  358. "k", "nocleanup", flag=1, default=0)
  359. self.add("strip_ansi", "supervisord.strip_ansi",
  360. "t", "strip_ansi", flag=1, default=0)
  361. self.add("profile_options", "supervisord.profile_options",
  362. "", "profile_options=", profile_options, default=None)
  363. self.pidhistory = {}
  364. self.process_group_configs = []
  365. self.parse_warnings = []
  366. self.signal_receiver = SignalReceiver()
  367. def version(self, dummy):
  368. """Print version to stdout and exit(0).
  369. """
  370. self.stdout.write('%s\n' % VERSION)
  371. self.exit(0)
  372. def getLogger(self, filename, level, fmt, rotating=False, maxbytes=0,
  373. backups=0, stdout=False):
  374. return loggers.getLogger(filename, level, fmt, rotating, maxbytes,
  375. backups, stdout)
  376. def realize(self, *arg, **kw):
  377. Options.realize(self, *arg, **kw)
  378. section = self.configroot.supervisord
  379. # Additional checking of user option; set uid and gid
  380. if self.user is not None:
  381. uid = name_to_uid(self.user)
  382. if uid is None:
  383. self.usage("No such user %s" % self.user)
  384. self.uid = uid
  385. self.gid = gid_for_uid(uid)
  386. if not self.loglevel:
  387. self.loglevel = section.loglevel
  388. if self.logfile:
  389. logfile = self.logfile
  390. else:
  391. logfile = section.logfile
  392. self.logfile = normalize_path(logfile)
  393. if self.pidfile:
  394. pidfile = self.pidfile
  395. else:
  396. pidfile = section.pidfile
  397. self.pidfile = normalize_path(pidfile)
  398. self.rpcinterface_factories = section.rpcinterface_factories
  399. self.serverurl = None
  400. self.server_configs = sconfigs = section.server_configs
  401. # we need to set a fallback serverurl that process.spawn can use
  402. # prefer a unix domain socket
  403. for config in [ config for config in sconfigs if
  404. config['family'] is socket.AF_UNIX ]:
  405. path = config['file']
  406. self.serverurl = 'unix://%s' % path
  407. break
  408. # fall back to an inet socket
  409. if self.serverurl is None:
  410. for config in [ config for config in sconfigs if
  411. config['family'] is socket.AF_INET]:
  412. host = config['host']
  413. port = config['port']
  414. if not host:
  415. host = 'localhost'
  416. self.serverurl = 'http://%s:%s' % (host, port)
  417. # self.serverurl may still be None if no servers at all are
  418. # configured in the config file
  419. self.identifier = section.identifier
  420. def process_config_file(self, do_usage=True):
  421. Options.process_config_file(self, do_usage=do_usage)
  422. new = self.configroot.supervisord.process_group_configs
  423. self.process_group_configs = new
  424. def read_config(self, fp):
  425. # Clear parse warnings, since we may be re-reading the
  426. # config a second time after a reload.
  427. self.parse_warnings = []
  428. section = self.configroot.supervisord
  429. if not hasattr(fp, 'read'):
  430. try:
  431. fp = open(fp, 'r')
  432. except (IOError, OSError):
  433. raise ValueError("could not find config file %s" % fp)
  434. parser = UnhosedConfigParser()
  435. try:
  436. parser.readfp(fp)
  437. except ConfigParser.ParsingError, why:
  438. raise ValueError(str(why))
  439. if parser.has_section('include'):
  440. if not parser.has_option('include', 'files'):
  441. raise ValueError(".ini file has [include] section, but no "
  442. "files setting")
  443. files = parser.get('include', 'files')
  444. files = files.split()
  445. if hasattr(fp, 'name'):
  446. base = os.path.dirname(os.path.abspath(fp.name))
  447. else:
  448. base = '.'
  449. for pattern in files:
  450. pattern = os.path.join(base, pattern)
  451. for filename in glob.glob(pattern):
  452. self.parse_warnings.append(
  453. 'Included extra file "%s" during parsing' % filename)
  454. try:
  455. parser.read(filename)
  456. except ConfigParser.ParsingError, why:
  457. raise ValueError(str(why))
  458. sections = parser.sections()
  459. if not 'supervisord' in sections:
  460. raise ValueError, '.ini file does not include supervisord section'
  461. get = parser.getdefault
  462. section.minfds = integer(get('minfds', 1024))
  463. section.minprocs = integer(get('minprocs', 200))
  464. directory = get('directory', None)
  465. if directory is None:
  466. section.directory = None
  467. else:
  468. section.directory = existing_directory(directory)
  469. section.user = get('user', None)
  470. section.umask = octal_type(get('umask', '022'))
  471. section.logfile = existing_dirpath(get('logfile', 'supervisord.log'))
  472. section.logfile_maxbytes = byte_size(get('logfile_maxbytes', '50MB'))
  473. section.logfile_backups = integer(get('logfile_backups', 10))
  474. section.loglevel = logging_level(get('loglevel', 'info'))
  475. section.pidfile = existing_dirpath(get('pidfile', 'supervisord.pid'))
  476. section.identifier = get('identifier', 'supervisor')
  477. section.nodaemon = boolean(get('nodaemon', 'false'))
  478. tempdir = tempfile.gettempdir()
  479. section.childlogdir = existing_directory(get('childlogdir', tempdir))
  480. section.nocleanup = boolean(get('nocleanup', 'false'))
  481. section.strip_ansi = boolean(get('strip_ansi', 'false'))
  482. expansions = {'here':self.here}
  483. expansions.update(environ_expansions())
  484. environ_str = get('environment', '')
  485. environ_str = expand(environ_str, expansions, 'environment')
  486. section.environment = dict_of_key_value_pairs(environ_str)
  487. # Process rpcinterface plugins before groups to allow custom events to
  488. # be registered.
  489. section.rpcinterface_factories = self.get_plugins(
  490. parser,
  491. 'supervisor.rpcinterface_factory',
  492. 'rpcinterface:'
  493. )
  494. section.process_group_configs = self.process_groups_from_parser(parser)
  495. for group in section.process_group_configs:
  496. for proc in group.process_configs:
  497. env = section.environment.copy()
  498. env.update(proc.environment)
  499. proc.environment = env
  500. section.server_configs = self.server_configs_from_parser(parser)
  501. section.profile_options = None
  502. return section
  503. def process_groups_from_parser(self, parser):
  504. groups = []
  505. all_sections = parser.sections()
  506. homogeneous_exclude = []
  507. get = parser.saneget
  508. # process heterogeneous groups
  509. for section in all_sections:
  510. if not section.startswith('group:'):
  511. continue
  512. group_name = section.split(':', 1)[1]
  513. programs = list_of_strings(get(section, 'programs', None))
  514. priority = integer(get(section, 'priority', 999))
  515. group_processes = []
  516. for program in programs:
  517. program_section = "program:%s" % program
  518. if not program_section in all_sections:
  519. raise ValueError(
  520. '[%s] names unknown program %s' % (section, program))
  521. homogeneous_exclude.append(program_section)
  522. processes = self.processes_from_section(parser, program_section,
  523. group_name,
  524. ProcessConfig)
  525. group_processes.extend(processes)
  526. groups.append(
  527. ProcessGroupConfig(self, group_name, priority, group_processes)
  528. )
  529. # process "normal" homogeneous groups
  530. for section in all_sections:
  531. if ( (not section.startswith('program:') )
  532. or section in homogeneous_exclude ):
  533. continue
  534. program_name = section.split(':', 1)[1]
  535. priority = integer(get(section, 'priority', 999))
  536. processes=self.processes_from_section(parser, section, program_name,
  537. ProcessConfig)
  538. groups.append(
  539. ProcessGroupConfig(self, program_name, priority, processes)
  540. )
  541. # process "event listener" homogeneous groups
  542. for section in all_sections:
  543. if not section.startswith('eventlistener:'):
  544. continue
  545. pool_name = section.split(':', 1)[1]
  546. # give listeners a "high" default priority so they are started first
  547. # and stopped last at mainloop exit
  548. priority = integer(get(section, 'priority', -1))
  549. buffer_size = integer(get(section, 'buffer_size', 10))
  550. result_handler = get(section, 'result_handler',
  551. 'supervisor.dispatchers:default_handler')
  552. try:
  553. result_handler = self.import_spec(result_handler)
  554. except ImportError:
  555. raise ValueError('%s cannot be resolved within [%s]' % (
  556. result_handler, section))
  557. pool_event_names = [x.upper() for x in
  558. list_of_strings(get(section, 'events', ''))]
  559. pool_event_names = set(pool_event_names)
  560. if not pool_event_names:
  561. raise ValueError('[%s] section requires an "events" line' %
  562. section)
  563. from supervisor.events import EventTypes
  564. pool_events = []
  565. for pool_event_name in pool_event_names:
  566. pool_event = getattr(EventTypes, pool_event_name, None)
  567. if pool_event is None:
  568. raise ValueError('Unknown event type %s in [%s] events' %
  569. (pool_event_name, section))
  570. pool_events.append(pool_event)
  571. processes=self.processes_from_section(parser, section, pool_name,
  572. EventListenerConfig)
  573. groups.append(
  574. EventListenerPoolConfig(self, pool_name, priority, processes,
  575. buffer_size, pool_events,
  576. result_handler)
  577. )
  578. # process fastcgi homogeneous groups
  579. for section in all_sections:
  580. if ( (not section.startswith('fcgi-program:') )
  581. or section in homogeneous_exclude ):
  582. continue
  583. program_name = section.split(':', 1)[1]
  584. priority = integer(get(section, 'priority', 999))
  585. proc_uid = name_to_uid(get(section, 'user', None))
  586. socket_owner = get(section, 'socket_owner', None)
  587. if socket_owner is not None:
  588. try:
  589. socket_owner = colon_separated_user_group(socket_owner)
  590. except ValueError:
  591. raise ValueError('Invalid socket_owner value %s'
  592. % socket_owner)
  593. socket_mode = get(section, 'socket_mode', None)
  594. if socket_mode is not None:
  595. try:
  596. socket_mode = octal_type(socket_mode)
  597. except (TypeError, ValueError):
  598. raise ValueError('Invalid socket_mode value %s'
  599. % socket_mode)
  600. socket = get(section, 'socket', None)
  601. if not socket:
  602. raise ValueError('[%s] section requires a "socket" line' %
  603. section)
  604. expansions = {'here':self.here,
  605. 'program_name':program_name}
  606. expansions.update(environ_expansions())
  607. socket = expand(socket, expansions, 'socket')
  608. try:
  609. socket_config = self.parse_fcgi_socket(socket, proc_uid,
  610. socket_owner, socket_mode)
  611. except ValueError, e:
  612. raise ValueError('%s in [%s] socket' % (str(e), section))
  613. processes=self.processes_from_section(parser, section, program_name,
  614. FastCGIProcessConfig)
  615. groups.append(
  616. FastCGIGroupConfig(self, program_name, priority, processes,
  617. socket_config)
  618. )
  619. groups.sort()
  620. return groups
  621. def parse_fcgi_socket(self, sock, proc_uid, socket_owner, socket_mode):
  622. if sock.startswith('unix://'):
  623. path = sock[7:]
  624. #Check it's an absolute path
  625. if not os.path.isabs(path):
  626. raise ValueError("Unix socket path %s is not an absolute path",
  627. path)
  628. path = normalize_path(path)
  629. if socket_owner is None:
  630. uid = os.getuid()
  631. if proc_uid is not None and proc_uid != uid:
  632. socket_owner = (proc_uid, self.get_gid_for_uid(proc_uid))
  633. if socket_mode is None:
  634. socket_mode = 0700
  635. return UnixStreamSocketConfig(path, owner=socket_owner,
  636. mode=socket_mode)
  637. if socket_owner is not None or socket_mode is not None:
  638. raise ValueError("socket_owner and socket_mode params should"
  639. + " only be used with a Unix domain socket")
  640. m = re.match(r'tcp://([^\s:]+):(\d+)$', sock)
  641. if m:
  642. host = m.group(1)
  643. port = int(m.group(2))
  644. return InetStreamSocketConfig(host, port)
  645. raise ValueError("Bad socket format %s", sock)
  646. def get_gid_for_uid(self, uid):
  647. pwrec = pwd.getpwuid(uid)
  648. return pwrec[3]
  649. def processes_from_section(self, parser, section, group_name,
  650. klass=None):
  651. if klass is None:
  652. klass = ProcessConfig
  653. programs = []
  654. get = parser.saneget
  655. program_name = section.split(':', 1)[1]
  656. priority = integer(get(section, 'priority', 999))
  657. autostart = boolean(get(section, 'autostart', 'true'))
  658. autorestart = auto_restart(get(section, 'autorestart', 'unexpected'))
  659. startsecs = integer(get(section, 'startsecs', 1))
  660. startretries = integer(get(section, 'startretries', 3))
  661. uid = name_to_uid(get(section, 'user', None))
  662. stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))
  663. stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))
  664. stopasgroup = boolean(get(section, 'stopasgroup', 'false'))
  665. killasgroup = boolean(get(section, 'killasgroup', stopasgroup))
  666. exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0,2'))
  667. redirect_stderr = boolean(get(section, 'redirect_stderr','false'))
  668. numprocs = integer(get(section, 'numprocs', 1))
  669. numprocs_start = integer(get(section, 'numprocs_start', 0))
  670. process_name = get(section, 'process_name', '%(program_name)s')
  671. environment_str = get(section, 'environment', '')
  672. stdout_cmaxbytes = byte_size(get(section,'stdout_capture_maxbytes','0'))
  673. stdout_events = boolean(get(section, 'stdout_events_enabled','false'))
  674. stderr_cmaxbytes = byte_size(get(section,'stderr_capture_maxbytes','0'))
  675. stderr_events = boolean(get(section, 'stderr_events_enabled','false'))
  676. serverurl = get(section, 'serverurl', None)
  677. if serverurl and serverurl.strip().upper() == 'AUTO':
  678. serverurl = None
  679. umask = get(section, 'umask', None)
  680. if umask is not None:
  681. umask = octal_type(umask)
  682. command = get(section, 'command', None)
  683. if command is None:
  684. raise ValueError, (
  685. 'program section %s does not specify a command' % section)
  686. if numprocs > 1:
  687. if process_name.find('%(process_num)') == -1:
  688. # process_name needs to include process_num when we
  689. # represent a group of processes
  690. raise ValueError(
  691. '%(process_num) must be present within process_name when '
  692. 'numprocs > 1')
  693. if stopasgroup and not killasgroup:
  694. raise ValueError("Cannot set stopasgroup=true and killasgroup=false")
  695. host_node_name = platform.node()
  696. for process_num in range(numprocs_start, numprocs + numprocs_start):
  697. expansions = {'here':self.here,
  698. 'process_num':process_num,
  699. 'program_name':program_name,
  700. 'host_node_name':host_node_name,
  701. 'group_name':group_name}
  702. expansions.update(environ_expansions())
  703. environment = dict_of_key_value_pairs(
  704. expand(environment_str, expansions, 'environment'))
  705. directory = get(section, 'directory', None)
  706. if directory:
  707. directory = expand(directory, expansions, 'directory')
  708. logfiles = {}
  709. for k in ('stdout', 'stderr'):
  710. n = '%s_logfile' % k
  711. lf_val = get(section, n, Automatic)
  712. if isinstance(lf_val, basestring):
  713. lf_val = expand(lf_val, expansions, n)
  714. lf_val = logfile_name(lf_val)
  715. logfiles[n] = lf_val
  716. bu_key = '%s_logfile_backups' % k
  717. backups = integer(get(section, bu_key, 10))
  718. logfiles[bu_key] = backups
  719. mb_key = '%s_logfile_maxbytes' % k
  720. maxbytes = byte_size(get(section, mb_key, '50MB'))
  721. logfiles[mb_key] = maxbytes
  722. if lf_val is Automatic and not maxbytes:
  723. self.parse_warnings.append(
  724. 'For [%s], AUTO logging used for %s without '
  725. 'rollover, set maxbytes > 0 to avoid filling up '
  726. 'filesystem unintentionally' % (section, n))
  727. pconfig = klass(
  728. self,
  729. name=expand(process_name, expansions, 'process_name'),
  730. command=expand(command, expansions, 'command'),
  731. directory=directory,
  732. umask=umask,
  733. priority=priority,
  734. autostart=autostart,
  735. autorestart=autorestart,
  736. startsecs=startsecs,
  737. startretries=startretries,
  738. uid=uid,
  739. stdout_logfile=logfiles['stdout_logfile'],
  740. stdout_capture_maxbytes = stdout_cmaxbytes,
  741. stdout_events_enabled = stdout_events,
  742. stdout_logfile_backups=logfiles['stdout_logfile_backups'],
  743. stdout_logfile_maxbytes=logfiles['stdout_logfile_maxbytes'],
  744. stderr_logfile=logfiles['stderr_logfile'],
  745. stderr_capture_maxbytes = stderr_cmaxbytes,
  746. stderr_events_enabled = stderr_events,
  747. stderr_logfile_backups=logfiles['stderr_logfile_backups'],
  748. stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],
  749. stopsignal=stopsignal,
  750. stopwaitsecs=stopwaitsecs,
  751. stopasgroup=stopasgroup,
  752. killasgroup=killasgroup,
  753. exitcodes=exitcodes,
  754. redirect_stderr=redirect_stderr,
  755. environment=environment,
  756. serverurl=serverurl)
  757. programs.append(pconfig)
  758. programs.sort() # asc by priority
  759. return programs
  760. def _parse_servernames(self, parser, stype):
  761. options = []
  762. for section in parser.sections():
  763. if section.startswith(stype):
  764. parts = section.split(':', 1)
  765. if len(parts) > 1:
  766. name = parts[1]
  767. else:
  768. name = None # default sentinel
  769. options.append((name, section))
  770. return options
  771. def _parse_username_and_password(self, parser, section):
  772. get = parser.saneget
  773. username = get(section, 'username', None)
  774. password = get(section, 'password', None)
  775. if username is None and password is not None:
  776. raise ValueError(
  777. 'Must specify username if password is specified in [%s]'
  778. % section)
  779. return {'username':username, 'password':password}
  780. def server_configs_from_parser(self, parser):
  781. configs = []
  782. inet_serverdefs = self._parse_servernames(parser, 'inet_http_server')
  783. for name, section in inet_serverdefs:
  784. config = {}
  785. get = parser.saneget
  786. config.update(self._parse_username_and_password(parser, section))
  787. config['name'] = name
  788. config['family'] = socket.AF_INET
  789. port = get(section, 'port', None)
  790. if port is None:
  791. raise ValueError('section [%s] has no port value' % section)
  792. host, port = inet_address(port)
  793. config['host'] = host
  794. config['port'] = port
  795. config['section'] = section
  796. configs.append(config)
  797. unix_serverdefs = self._parse_servernames(parser, 'unix_http_server')
  798. for name, section in unix_serverdefs:
  799. config = {}
  800. get = parser.saneget
  801. sfile = get(section, 'file', None)
  802. if sfile is None:
  803. raise ValueError('section [%s] has no file value' % section)
  804. sfile = sfile.strip()
  805. config['name'] = name
  806. config['family'] = socket.AF_UNIX
  807. sfile = expand(sfile, {'here':self.here}, 'socket file')
  808. config['file'] = normalize_path(sfile)
  809. config.update(self._parse_username_and_password(parser, section))
  810. chown = get(section, 'chown', None)
  811. if chown is not None:
  812. try:
  813. chown = colon_separated_user_group(chown)
  814. except ValueError:
  815. raise ValueError('Invalid sockchown value %s' % chown)
  816. else:
  817. chown = (-1, -1)
  818. config['chown'] = chown
  819. chmod = get(section, 'chmod', None)
  820. if chmod is not None:
  821. try:
  822. chmod = octal_type(chmod)
  823. except (TypeError, ValueError):
  824. raise ValueError('Invalid chmod value %s' % chmod)
  825. else:
  826. chmod = 0700
  827. config['chmod'] = chmod
  828. config['section'] = section
  829. configs.append(config)
  830. return configs
  831. def daemonize(self):
  832. # To daemonize, we need to become the leader of our own session
  833. # (process) group. If we do not, signals sent to our
  834. # parent process will also be sent to us. This might be bad because
  835. # signals such as SIGINT can be sent to our parent process during
  836. # normal (uninteresting) operations such as when we press Ctrl-C in the
  837. # parent terminal window to escape from a logtail command.
  838. # To disassociate ourselves from our parent's session group we use
  839. # os.setsid. It means "set session id", which has the effect of
  840. # disassociating a process from is current session and process group
  841. # and setting itself up as a new session leader.
  842. #
  843. # Unfortunately we cannot call setsid if we're already a session group
  844. # leader, so we use "fork" to make a copy of ourselves that is
  845. # guaranteed to not be a session group leader.
  846. #
  847. # We also change directories, set stderr and stdout to null, and
  848. # change our umask.
  849. #
  850. # This explanation was (gratefully) garnered from
  851. # http://www.hawklord.uklinux.net/system/daemons/d3.htm
  852. pid = os.fork()
  853. if pid != 0:
  854. # Parent
  855. self.logger.blather("supervisord forked; parent exiting")
  856. os._exit(0)
  857. # Child
  858. self.logger.info("daemonizing the supervisord process")
  859. if self.directory:
  860. try:
  861. os.chdir(self.directory)
  862. except OSError, err:
  863. self.logger.critical("can't chdir into %r: %s"
  864. % (self.directory, err))
  865. else:
  866. self.logger.info("set current directory: %r"
  867. % self.directory)
  868. os.close(0)
  869. self.stdin = sys.stdin = sys.__stdin__ = open("/dev/null")
  870. os.close(1)
  871. self.stdout = sys.stdout = sys.__stdout__ = open("/dev/null", "w")
  872. os.close(2)
  873. self.stderr = sys.stderr = sys.__stderr__ = open("/dev/null", "w")
  874. os.setsid()
  875. os.umask(self.umask)
  876. # XXX Stevens, in his Advanced Unix book, section 13.3 (page
  877. # 417) recommends calling umask(0) and closing unused
  878. # file descriptors. In his Network Programming book, he
  879. # additionally recommends ignoring SIGHUP and forking again
  880. # after the setsid() call, for obscure SVR4 reasons.
  881. def write_pidfile(self):
  882. pid = os.getpid()
  883. try:
  884. f = open(self.pidfile, 'w')
  885. f.write('%s\n' % pid)
  886. f.close()
  887. except (IOError, OSError):
  888. self.logger.critical('could not write pidfile %s' % self.pidfile)
  889. else:
  890. self.logger.info('supervisord started with pid %s' % pid)
  891. def cleanup(self):
  892. try:
  893. for config, server in self.httpservers:
  894. if config['family'] == socket.AF_UNIX:
  895. if self.unlink_socketfiles:
  896. socketname = config['file']
  897. try:
  898. os.unlink(socketname)
  899. except OSError:
  900. pass
  901. except OSError:
  902. pass
  903. try:
  904. os.unlink(self.pidfile)
  905. except OSError:
  906. pass
  907. def close_httpservers(self):
  908. for config, server in self.httpservers:
  909. server.close()
  910. map = self.get_socket_map()
  911. # server._map is a reference to the asyncore socket_map
  912. for dispatcher in map.values():
  913. # For unknown reasons, sometimes an http_channel
  914. # dispatcher in the socket map related to servers
  915. # remains open *during a reload*. If one of these
  916. # exists at this point, we need to close it by hand
  917. # (thus removing it from the asyncore.socket_map). If
  918. # we don't do this, 'cleanup_fds' will cause its file
  919. # descriptor to be closed, but it will still remain in
  920. # the socket_map, and eventually its file descriptor
  921. # will be passed to # select(), which will bomb. See
  922. # also http://www.plope.com/software/collector/253
  923. dispatcher_server = getattr(dispatcher, 'server', None)
  924. if dispatcher_server is server:
  925. dispatcher.close()
  926. def close_logger(self):
  927. self.logger.close()
  928. def setsignals(self):
  929. receive = self.signal_receiver.receive
  930. signal.signal(signal.SIGTERM, receive)
  931. signal.signal(signal.SIGINT, receive)
  932. signal.signal(signal.SIGQUIT, receive)
  933. signal.signal(signal.SIGHUP, receive)
  934. signal.signal(signal.SIGCHLD, receive)
  935. signal.signal(signal.SIGUSR2, receive)
  936. def get_signal(self):
  937. return self.signal_receiver.get_signal()
  938. def openhttpservers(self, supervisord):
  939. try:
  940. self.httpservers = self.make_http_servers(supervisord)
  941. except socket.error, why:
  942. if why[0] == errno.EADDRINUSE:
  943. self.usage('Another program is already listening on '
  944. 'a port that one of our HTTP servers is '
  945. 'configured to use. Shut this program '
  946. 'down first before starting supervisord.')
  947. else:
  948. help = 'Cannot open an HTTP server: socket.error reported'
  949. errorname = errno.errorcode.get(why[0])
  950. if errorname is None:
  951. self.usage('%s %s' % (help, why[0]))
  952. else:
  953. self.usage('%s errno.%s (%d)' %
  954. (help, errorname, why[0]))
  955. self.unlink_socketfiles = False
  956. except ValueError, why:
  957. self.usage(why[0])
  958. def get_autochildlog_name(self, name, identifier, channel):
  959. prefix='%s-%s---%s-' % (name, channel, identifier)
  960. logfile = self.mktempfile(
  961. suffix='.log',
  962. prefix=prefix,
  963. dir=self.childlogdir)
  964. return logfile
  965. def clear_autochildlogdir(self):
  966. # must be called after realize()
  967. childlogdir = self.childlogdir
  968. fnre = re.compile(r'.+?---%s-\S+\.log\.{0,1}\d{0,4}' % self.identifier)
  969. try:
  970. filenames = os.listdir(childlogdir)
  971. except (IOError, OSError):
  972. self.logger.warn('Could not clear childlog dir')
  973. return
  974. for filename in filenames:
  975. if fnre.match(filename):
  976. pathname = os.path.join(childlogdir, filename)
  977. try:
  978. os.remove(pathname)
  979. except (OSError, IOError):
  980. self.logger.warn('Failed to clean up %r' % pathname)
  981. def get_socket_map(self):
  982. return asyncore.socket_map
  983. def cleanup_fds(self):
  984. # try to close any leaked file descriptors (for reload)
  985. start = 5
  986. for x in range(start, self.minfds):
  987. try:
  988. os.close(x)
  989. except OSError:
  990. pass
  991. def select(self, r, w, x, timeout):
  992. return select.select(r, w, x, timeout)
  993. def kill(self, pid, signal):
  994. os.kill(pid, signal)
  995. def set_uid(self):
  996. if self.uid is None:
  997. if os.getuid() == 0:
  998. return 'Supervisor running as root (no user in config file)'
  999. return None
  1000. msg = self.dropPrivileges(self.uid)
  1001. if msg is None:
  1002. return 'Set uid to user %s' % self.uid
  1003. return msg
  1004. def dropPrivileges(self, user):
  1005. # Drop root privileges if we have them
  1006. if user is None:
  1007. return "No user specified to setuid to!"
  1008. if os.getuid() != 0:
  1009. return "Can't drop privilege as nonroot user"
  1010. try:
  1011. uid = int(user)
  1012. except ValueError:
  1013. try:
  1014. pwrec = pwd.getpwnam(user)
  1015. except KeyError:
  1016. return "Can't find username %r" % user
  1017. uid = pwrec[2]
  1018. else:
  1019. try:
  1020. pwrec = pwd.getpwuid(uid)
  1021. except KeyError:
  1022. return "Can't find uid %r" % uid
  1023. gid = pwrec[3]
  1024. if hasattr(os, 'setgroups'):
  1025. user = pwrec[0]
  1026. groups = [grprec[2] for grprec in grp.getgrall() if user in
  1027. grprec[3]]
  1028. # always put our primary gid first in this list, otherwise we can
  1029. # lose group info since sometimes the first group in the setgroups
  1030. # list gets overwritten on the subsequent setgid call (at least on
  1031. # freebsd 9 with python 2.7 - this will be safe though for all unix
  1032. # /python version combos)
  1033. groups.insert(0, gid)
  1034. try:
  1035. os.setgroups(groups)
  1036. except OSError:
  1037. return 'Could not set groups of effective user'
  1038. try:
  1039. os.setgid(gid)
  1040. except OSError:
  1041. return 'Could not set group id of effective user'
  1042. os.setuid(uid)
  1043. def waitpid(self):
  1044. # need pthread_sigmask here to avoid concurrent sigchild, but
  1045. # Python doesn't offer it as it's not standard across UNIX versions.
  1046. # there is still a race condition here; we can get a sigchild while
  1047. # we're sitting in the waitpid call.
  1048. try:
  1049. pid, sts = os.waitpid(-1, os.WNOHANG)
  1050. except OSError, why:
  1051. err = why[0]
  1052. if err not in (errno.ECHILD, errno.EINTR):
  1053. self.logger.critical(
  1054. 'waitpid error; a process may not be cleaned up properly')
  1055. if err == errno.EINTR:
  1056. self.logger.blather('EINTR during reap')
  1057. pid, sts = None, None
  1058. return pid, sts
  1059. def set_rlimits(self):
  1060. limits = []
  1061. if hasattr(resource, 'RLIMIT_NOFILE'):
  1062. limits.append(
  1063. {
  1064. 'msg':('The minimum number of file descriptors required '
  1065. 'to run this process is %(min)s as per the "minfds" '
  1066. 'command-line argument or config file setting. '
  1067. 'The current environment will only allow you '
  1068. 'to open %(hard)s file descriptors. Either raise '
  1069. 'the number of usable file descriptors in your '
  1070. 'environment (see README.rst) or lower the '
  1071. 'minfds setting in the config file to allow '
  1072. 'the process to start.'),
  1073. 'min':self.minfds,
  1074. 'resource':resource.RLIMIT_NOFILE,
  1075. 'name':'RLIMIT_NOFILE',
  1076. })
  1077. if hasattr(resource, 'RLIMIT_NPROC'):
  1078. limits.append(
  1079. {
  1080. 'msg':('The minimum number of available processes required '
  1081. 'to run this program is %(min)s as per the "minprocs" '
  1082. 'command-line argument or config file setting. '
  1083. 'The current environment will only allow you '
  1084. 'to open %(hard)s processes. Either raise '
  1085. 'the number of usable processes in your '
  1086. 'environment (see README.rst) or lower the '
  1087. 'minprocs setting in the config file to allow '
  1088. 'the program to start.'),
  1089. 'min':self.minprocs,
  1090. 'resource':resource.RLIMIT_NPROC,
  1091. 'name':'RLIMIT_NPROC',
  1092. })
  1093. msgs = []
  1094. for limit in limits:
  1095. min = limit['min']
  1096. res = limit['resource']
  1097. msg = limit['msg']
  1098. name = limit['name']
  1099. soft, hard = resource.getrlimit(res)
  1100. if (soft < min) and (soft != -1): # -1 means unlimited
  1101. if (hard < min) and (hard != -1):
  1102. # setrlimit should increase the hard limit if we are
  1103. # root, if not then setrlimit raises and we print usage
  1104. hard = min
  1105. try:
  1106. resource.setrlimit(res, (min, hard))
  1107. msgs.append('Increased %(name)s limit to %(min)s' %
  1108. locals())
  1109. except (resource.error, ValueError):
  1110. self.usage(msg % locals())
  1111. return msgs
  1112. def make_logger(self, critical_messages, warn_messages, info_messages):
  1113. # must be called after realize() and after supervisor does setuid()
  1114. format = '%(asctime)s %(levelname)s %(message)s\n'
  1115. self.logger = loggers.getLogger(
  1116. self.logfile,
  1117. self.loglevel,
  1118. format,
  1119. rotating=True,
  1120. maxbytes=self.logfile_maxbytes,
  1121. backups=self.logfile_backups,
  1122. stdout = self.nodaemon,
  1123. )
  1124. for msg in critical_messages:
  1125. self.logger.critical(msg)
  1126. for msg in warn_messages:
  1127. self.logger.warn(msg)
  1128. for msg in info_messages:
  1129. self.logger.info(msg)
  1130. def make_http_servers(self, supervisord):
  1131. from supervisor.http import make_http_servers
  1132. return make_http_servers(self, supervisord)
  1133. def close_fd(self, fd):
  1134. try:
  1135. os.close(fd)
  1136. except OSError:
  1137. pass
  1138. def fork(self):
  1139. return os.fork()
  1140. def dup2(self, frm, to):
  1141. return os.dup2(frm, to)
  1142. def setpgrp(self):
  1143. return os.setpgrp()
  1144. def stat(self, filename):
  1145. return os.stat(filename)
  1146. def write(self, fd, data):
  1147. return os.write(fd, data)
  1148. def execve(self, filename, argv, env):
  1149. return os.execve(filename, argv, env)
  1150. def mktempfile(self, suffix, prefix, dir):
  1151. # set os._urandomfd as a hack around bad file descriptor bug
  1152. # seen in the wild, see
  1153. # http://www.plope.com/software/collector/252
  1154. os._urandomfd = None
  1155. fd, filename = tempfile.mkstemp(suffix, prefix, dir)
  1156. os.close(fd)
  1157. return filename
  1158. def remove(self, path):
  1159. os.remove(path)
  1160. def exists(self, path):
  1161. return os.path.exists(path)
  1162. def _exit(self, code):
  1163. os._exit(code)
  1164. def setumask(self, mask):
  1165. os.umask(mask)
  1166. def get_path(self):
  1167. """Return a list corresponding to $PATH, or a default."""
  1168. path = ["/bin", "/usr/bin", "/usr/local/bin"]
  1169. if os.environ.has_key("PATH"):
  1170. p = os.environ["PATH"]
  1171. if p:
  1172. path = p.split(os.pathsep)
  1173. return path
  1174. def get_pid(self):
  1175. return os.getpid()
  1176. def check_execv_args(self, filename, argv, st):
  1177. if st is None:
  1178. raise NotFound("can't find command %r" % filename)
  1179. elif stat.S_ISDIR(st[stat.ST_MODE]):
  1180. raise NotExecutable("command at %r is a directory" % filename)
  1181. elif not (stat.S_IMODE(st[stat.ST_MODE]) & 0111):
  1182. raise NotExecutable("command at %r is not executable" % filename)
  1183. elif not os.access(filename, os.X_OK):
  1184. raise NoPermission("no permission to run command %r" % filename)
  1185. def reopenlogs(self):
  1186. self.logger.info('supervisord logreopen')
  1187. for handler in self.logger.handlers:
  1188. if hasattr(handler, 'reopen'):
  1189. handler.reopen()
  1190. def readfd(self, fd):
  1191. try:
  1192. data = os.read(fd, 2 << 16) # 128K
  1193. except OSError, why:
  1194. if why[0] not in (errno.EWOULDBLOCK, errno.EBADF, errno.EINTR):
  1195. raise
  1196. data = ''
  1197. return data
  1198. def process_environment(self):
  1199. os.environ.update(self.environment or {})
  1200. def open(self, fn, mode='r'):
  1201. return open(fn, mode)
  1202. def chdir(self, dir):
  1203. os.chdir(dir)
  1204. def make_pipes(self, stderr=True):
  1205. """ Create pipes for parent to child stdin/stdout/stderr
  1206. communications. Open fd in nonblocking mode so we can read them
  1207. in the mainloop without blocking. If stderr is False, don't
  1208. create a pipe for stderr. """
  1209. pipes = {'child_stdin':None,
  1210. 'stdin':None,
  1211. 'stdout':None,
  1212. 'child_stdout':None,
  1213. 'stderr':None,
  1214. 'child_stderr':None}
  1215. try:
  1216. stdin, child_stdin = os.pipe()
  1217. pipes['child_stdin'], pipes['stdin'] = stdin, child_stdin
  1218. stdout, child_stdout = os.pipe()
  1219. pipes['stdout'], pipes['child_stdout'] = stdout, child_stdout
  1220. if stderr:
  1221. stderr, child_stderr = os.pipe()
  1222. pipes['stderr'], pipes['child_stderr'] = stderr, child_stderr
  1223. for fd in (pipes['stdout'], pipes['stderr'], pipes['stdin']):
  1224. if fd is not None:
  1225. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NDELAY)
  1226. return pipes
  1227. except OSError:
  1228. for fd in pipes.values():
  1229. if fd is not None:
  1230. self.close_fd(fd)
  1231. def close_parent_pipes(self, pipes):
  1232. for fdname in ('stdin', 'stdout', 'stderr'):
  1233. fd = pipes[fdname]
  1234. if fd is not None:
  1235. self.close_fd(fd)
  1236. def close_child_pipes(self, pipes):
  1237. for fdname in ('child_stdin', 'child_stdout', 'child_stderr'):
  1238. fd = pipes[fdname]
  1239. if fd is not None:
  1240. self.close_fd(fd)
  1241. class ClientOptions(Options):
  1242. positional_args_allowed = 1
  1243. interactive = None
  1244. prompt = None
  1245. serverurl = None
  1246. username = None
  1247. password = None
  1248. history_file = None
  1249. def __init__(self):
  1250. Options.__init__(self)
  1251. self.configroot = Dummy()
  1252. self.configroot.supervisorctl = Dummy()
  1253. self.configroot.supervisorctl.interactive = None
  1254. self.configroot.supervisorctl.prompt = 'supervisor'
  1255. self.configroot.supervisorctl.serverurl = None
  1256. self.configroot.supervisorctl.username = None
  1257. self.configroot.supervisorctl.password = None
  1258. self.configroot.supervisorctl.history_file = None
  1259. self.add("interactive", "supervisorctl.interactive", "i",
  1260. "interactive", flag=1, default=0)
  1261. self.add("prompt", "supervisorctl.prompt", default="supervisor")
  1262. self.add("serverurl", "supervisorctl.serverurl", "s:", "serverurl=",
  1263. url, default="http://localhost:9001")
  1264. self.add("username", "supervisorctl.username", "u:", "username=")
  1265. self.add("password", "supervisorctl.password", "p:", "password=")
  1266. self.add("history", "supervisorctl.history_file", "r:", "history_file=")
  1267. def realize(self, *arg, **kw):
  1268. Options.realize(self, *arg, **kw)
  1269. if not self.args:
  1270. self.interactive = 1
  1271. def read_config(self, fp):
  1272. section = self.configroot.supervisorctl
  1273. if not hasattr(fp, 'read'):
  1274. self.here = os.path.dirname(normalize_path(fp))
  1275. try:
  1276. fp = open(fp, 'r')
  1277. except (IOError, OSError):
  1278. raise ValueError("could not find config file %s" % fp)
  1279. config = UnhosedConfigParser()
  1280. config.mysection = 'supervisorctl'
  1281. config.readfp(fp)
  1282. sections = config.sections()
  1283. if not 'supervisorctl' in sections:
  1284. raise ValueError,'.ini file does not include supervisorctl section'
  1285. serverurl = config.getdefault('serverurl', 'http://localhost:9001')
  1286. if serverurl.startswith('unix://'):
  1287. sf = serverurl[7:]
  1288. path = expand(sf, {'here':self.here}, 'serverurl')
  1289. path = normalize_path(path)
  1290. serverurl = 'unix://%s' % path
  1291. section.serverurl = serverurl
  1292. # The defaults used below are really set in __init__ (since
  1293. # section==self.configroot.supervisorctl)
  1294. section.prompt = config.getdefault('prompt', section.prompt)
  1295. section.username = config.getdefault('username', section.username)
  1296. section.password = config.getdefault('password', section.password)
  1297. history_file = config.getdefault('history_file', section.history_file)
  1298. if history_file:
  1299. history_file = normalize_path(history_file)
  1300. section.history_file = history_file
  1301. self.history_file = history_file
  1302. else:
  1303. section.history_file = None
  1304. self.history_file = None
  1305. from supervisor.supervisorctl import DefaultControllerPlugin
  1306. self.plugin_factories = self.get_plugins(
  1307. config,
  1308. 'supervisor.ctl_factory',
  1309. 'ctlplugin:'
  1310. )
  1311. default_factory = ('default', DefaultControllerPlugin, {})
  1312. # if you want to a supervisorctl without the default plugin,
  1313. # please write your own supervisorctl.
  1314. self.plugin_factories.insert(0, default_factory)
  1315. return section
  1316. def getServerProxy(self):
  1317. # mostly put here for unit testing
  1318. return xmlrpclib.ServerProxy(
  1319. # dumbass ServerProxy won't allow us to pass in a non-HTTP url,
  1320. # so we fake the url we pass into it and always use the transport's
  1321. # 'serverurl' to figure out what to attach to
  1322. 'http://127.0.0.1',
  1323. transport = xmlrpc.SupervisorTransport(self.username,
  1324. self.password,
  1325. self.serverurl)
  1326. )
  1327. _marker = []
  1328. class UnhosedConfigParser(ConfigParser.RawConfigParser):
  1329. mysection = 'supervisord'
  1330. def read_string(self, s):
  1331. from StringIO import StringIO
  1332. s = StringIO(s)
  1333. return self.readfp(s)
  1334. def getdefault(self, option, default=_marker):
  1335. try:
  1336. return self.get(self.mysection, option)
  1337. except ConfigParser.NoOptionError:
  1338. if default is _marker:
  1339. raise
  1340. else:
  1341. return default
  1342. def saneget(self, section, option, default=_marker):
  1343. try:
  1344. return self.get(section, option)
  1345. except ConfigParser.NoOptionError:
  1346. if default is _marker:
  1347. raise
  1348. else:
  1349. return default
  1350. class Config(object):
  1351. def __ne__(self, other):
  1352. return not self.__eq__(other)
  1353. def __lt__(self, other):
  1354. if self.priority == other.priority:
  1355. return self.name < other.name
  1356. return self.priority < other.priority
  1357. def __le__(self, other):
  1358. if self.priority == other.priority:
  1359. return self.name <= other.name
  1360. return self.priority <= other.priority
  1361. def __gt__(self, other):
  1362. if self.priority == other.priority:
  1363. return self.name > other.name
  1364. return self.priority > other.priority
  1365. def __ge__(self, other):
  1366. if self.priority == other.priority:
  1367. return self.name >= other.name
  1368. return self.priority >= other.priority
  1369. def __repr__(self):
  1370. return '<%s instance at %s named %s>' % (self.__class__, id(self),
  1371. self.name)
  1372. class ProcessConfig(Config):
  1373. req_param_names = [
  1374. 'name', 'uid', 'command', 'directory', 'umask', 'priority',
  1375. 'autostart', 'autorestart', 'startsecs', 'startretries',
  1376. 'stdout_logfile', 'stdout_capture_maxbytes',
  1377. 'stdout_events_enabled',
  1378. 'stdout_logfile_backups', 'stdout_logfile_maxbytes',
  1379. 'stderr_logfile', 'stderr_capture_maxbytes',
  1380. 'stderr_logfile_backups', 'stderr_logfile_maxbytes',
  1381. 'stderr_events_enabled',
  1382. 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
  1383. 'exitcodes', 'redirect_stderr' ]
  1384. optional_param_names = [ 'environment', 'serverurl' ]
  1385. def __init__(self, options, **params):
  1386. self.options = options
  1387. for name in self.req_param_names:
  1388. setattr(self, name, params[name])
  1389. for name in self.optional_param_names:
  1390. setattr(self, name, params.get(name, None))
  1391. def __eq__(self, other):
  1392. if not isinstance(other, ProcessConfig):
  1393. return False
  1394. for name in self.req_param_names + self.optional_param_names:
  1395. if Automatic in [getattr(self, name), getattr(other, name)] :
  1396. continue
  1397. if getattr(self, name) != getattr(other, name):
  1398. return False
  1399. return True
  1400. def create_autochildlogs(self):
  1401. # temporary logfiles which are erased at start time
  1402. get_autoname = self.options.get_autochildlog_name
  1403. sid = self.options.identifier
  1404. name = self.name
  1405. if self.stdout_logfile is Automatic:
  1406. self.stdout_logfile = get_autoname(name, sid, 'stdout')
  1407. if self.stderr_logfile is Automatic:
  1408. self.stderr_logfile = get_autoname(name, sid, 'stderr')
  1409. def make_process(self, group=None):
  1410. from supervisor.process import Subprocess
  1411. process = Subprocess(self)
  1412. process.group = group
  1413. return process
  1414. def make_dispatchers(self, proc):
  1415. use_stderr = not self.redirect_stderr
  1416. p = self.options.make_pipes(use_stderr)
  1417. stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']
  1418. dispatchers = {}
  1419. from supervisor.dispatchers import POutputDispatcher
  1420. from supervisor.dispatchers import PInputDispatcher
  1421. from supervisor import events
  1422. if stdout_fd is not None:
  1423. etype = events.ProcessCommunicationStdoutEvent
  1424. dispatchers[stdout_fd] = POutputDispatcher(proc, etype, stdout_fd)
  1425. if stderr_fd is not None:
  1426. etype = events.ProcessCommunicationStderrEvent
  1427. dispatchers[stderr_fd] = POutputDispatcher(proc,etype, stderr_fd)
  1428. if stdin_fd is not None:
  1429. dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)
  1430. return dispatchers, p
  1431. class EventListenerConfig(ProcessConfig):
  1432. def make_dispatchers(self, proc):
  1433. use_stderr = not self.redirect_stderr
  1434. p = self.options.make_pipes(use_stderr)
  1435. stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']
  1436. dispatchers = {}
  1437. from supervisor.dispatchers import PEventListenerDispatcher
  1438. from supervisor.dispatchers import PInputDispatcher
  1439. from supervisor.dispatchers import POutputDispatcher
  1440. from supervisor import events
  1441. if stdout_fd is not None:
  1442. dispatchers[stdout_fd] = PEventListenerDispatcher(proc, 'stdout',
  1443. stdout_fd)
  1444. if stderr_fd is not None:
  1445. etype = events.ProcessCommunicationStderrEvent
  1446. dispatchers[stderr_fd] = POutputDispatcher(proc, etype, stderr_fd)
  1447. if stdin_fd is not None:
  1448. dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)
  1449. return dispatchers, p
  1450. class FastCGIProcessConfig(ProcessConfig):
  1451. def make_process(self, group=None):
  1452. if group is None:
  1453. raise NotImplementedError('FastCGI programs require a group')
  1454. from supervisor.process import FastCGISubprocess
  1455. process = FastCGISubprocess(self)
  1456. process.group = group
  1457. return process
  1458. def make_dispatchers(self, proc):
  1459. dispatchers, p = ProcessConfig.make_dispatchers(self, proc)
  1460. #FastCGI child processes expect the FastCGI socket set to
  1461. #file descriptor 0, so supervisord cannot use stdin
  1462. #to communicate with the child process
  1463. stdin_fd = p['stdin']
  1464. if stdin_fd is not None:
  1465. dispatchers[stdin_fd].close()
  1466. return dispatchers, p
  1467. class ProcessGroupConfig(Config):
  1468. def __init__(self, options, name, priority, process_configs):
  1469. self.options = options
  1470. self.name = name
  1471. self.priority = priority
  1472. self.process_configs = process_configs
  1473. def __eq__(self, other):
  1474. if not isinstance(other, ProcessGroupConfig):
  1475. return False
  1476. if self.name != other.name:
  1477. return False
  1478. if self.priority != other.priority:
  1479. return False
  1480. if self.process_configs != other.process_configs:
  1481. return False
  1482. return True
  1483. def after_setuid(self):
  1484. for config in self.process_configs:
  1485. config.create_autochildlogs()
  1486. def make_group(self):
  1487. from supervisor.process import ProcessGroup
  1488. return ProcessGroup(self)
  1489. class EventListenerPoolConfig(Config):
  1490. def __init__(self, options, name, priority, process_configs, buffer_size,
  1491. pool_events, result_handler):
  1492. self.options = options
  1493. self.name = name
  1494. self.priority = priority
  1495. self.process_configs = process_configs
  1496. self.buffer_size = buffer_size
  1497. self.pool_events = pool_events
  1498. self.result_handler = result_handler
  1499. def __eq__(self, other):
  1500. if not isinstance(other, EventListenerPoolConfig):
  1501. return False
  1502. if (self.name == other.name) and (self.priority == other.priority):
  1503. return True
  1504. return False
  1505. def after_setuid(self):
  1506. for config in self.process_configs:
  1507. config.create_autochildlogs()
  1508. def make_group(self):
  1509. from supervisor.process import EventListenerPool
  1510. return EventListenerPool(self)
  1511. class FastCGIGroupConfig(ProcessGroupConfig):
  1512. def __init__(self, options, name, priority, process_configs,
  1513. socket_config):
  1514. self.options = options
  1515. self.name = name
  1516. self.priority = priority
  1517. self.process_configs = process_configs
  1518. self.socket_config = socket_config
  1519. def __eq__(self, other):
  1520. if not isinstance(other, FastCGIGroupConfig):
  1521. return False
  1522. if self.socket_config != other.socket_config:
  1523. return False
  1524. return ProcessGroupConfig.__eq__(self, other)
  1525. def make_group(self):
  1526. from supervisor.process import FastCGIProcessGroup
  1527. return FastCGIProcessGroup(self)
  1528. def readFile(filename, offset, length):
  1529. """ Read length bytes from the file named by filename starting at
  1530. offset """
  1531. absoffset = abs(offset)
  1532. abslength = abs(length)
  1533. try:
  1534. f = open(filename, 'rb')
  1535. if absoffset != offset:
  1536. # negative offset returns offset bytes from tail of the file
  1537. if length:
  1538. raise ValueError('BAD_ARGUMENTS')
  1539. f.seek(0, 2)
  1540. sz = f.tell()
  1541. pos = int(sz - absoffset)
  1542. if pos < 0:
  1543. pos = 0
  1544. f.seek(pos)
  1545. data = f.read(absoffset)
  1546. else:
  1547. if abslength != length:
  1548. raise ValueError('BAD_ARGUMENTS')
  1549. if length == 0:
  1550. f.seek(offset)
  1551. data = f.read()
  1552. else:
  1553. sz = f.seek(offset)
  1554. data = f.read(length)
  1555. except (OSError, IOError):
  1556. raise ValueError('FAILED')
  1557. return data
  1558. def tailFile(filename, offset, length):
  1559. """
  1560. Read length bytes from the file named by filename starting at
  1561. offset, automatically increasing offset and setting overflow
  1562. flag if log size has grown beyond (offset + length). If length
  1563. bytes are not available, as many bytes as are available are returned.
  1564. """
  1565. overflow = False
  1566. try:
  1567. f = open(filename, 'rb')
  1568. f.seek(0, 2)
  1569. sz = f.tell()
  1570. if sz > (offset + length):
  1571. overflow = True
  1572. offset = sz - 1
  1573. if (offset + length) > sz:
  1574. if (offset > (sz - 1)):
  1575. length = 0
  1576. offset = sz - length
  1577. if offset < 0: offset = 0
  1578. if length < 0: length = 0
  1579. if length == 0:
  1580. data = ''
  1581. else:
  1582. f.seek(offset)
  1583. data = f.read(length)
  1584. offset = sz
  1585. return [data, offset, overflow]
  1586. except (OSError, IOError):
  1587. return ['', offset, False]
  1588. # Helpers for dealing with signals and exit status
  1589. def decode_wait_status(sts):
  1590. """Decode the status returned by wait() or waitpid().
  1591. Return a tuple (exitstatus, message) where exitstatus is the exit
  1592. status, or -1 if the process was killed by a signal; and message
  1593. is a message telling what happened. It is the caller's
  1594. responsibility to display the message.
  1595. """
  1596. if os.WIFEXITED(sts):
  1597. es = os.WEXITSTATUS(sts) & 0xffff
  1598. msg = "exit status %s" % es
  1599. return es, msg
  1600. elif os.WIFSIGNALED(sts):
  1601. sig = os.WTERMSIG(sts)
  1602. msg = "terminated by %s" % signame(sig)
  1603. if hasattr(os, "WCOREDUMP"):
  1604. iscore = os.WCOREDUMP(sts)
  1605. else:
  1606. iscore = sts & 0x80
  1607. if iscore:
  1608. msg += " (core dumped)"
  1609. return -1, msg
  1610. else:
  1611. msg = "unknown termination cause 0x%04x" % sts
  1612. return -1, msg
  1613. _signames = None
  1614. def signame(sig):
  1615. """Return a symbolic name for a signal.
  1616. Return "signal NNN" if there is no corresponding SIG name in the
  1617. signal module.
  1618. """
  1619. if _signames is None:
  1620. _init_signames()
  1621. return _signames.get(sig) or "signal %d" % sig
  1622. def _init_signames():
  1623. global _signames
  1624. d = {}
  1625. for k, v in signal.__dict__.items():
  1626. k_startswith = getattr(k, "startswith", None)
  1627. if k_startswith is None:
  1628. continue
  1629. if k_startswith("SIG") and not k_startswith("SIG_"):
  1630. d[v] = k
  1631. _signames = d
  1632. class SignalReceiver:
  1633. def __init__(self):
  1634. self._signals_recvd = []
  1635. def receive(self, sig, frame):
  1636. if sig not in self._signals_recvd:
  1637. self._signals_recvd.append(sig)
  1638. def get_signal(self):
  1639. if self._signals_recvd:
  1640. sig = self._signals_recvd.pop(0)
  1641. else:
  1642. sig = None
  1643. return sig
  1644. # miscellaneous utility functions
  1645. def expand(s, expansions, name):
  1646. try:
  1647. return s % expansions
  1648. except KeyError:
  1649. raise ValueError(
  1650. 'Format string %r for %r contains names which cannot be '
  1651. 'expanded' % (s, name))
  1652. except:
  1653. raise ValueError(
  1654. 'Format string %r for %r is badly formatted' % (s, name)
  1655. )
  1656. _environ_expansions = None
  1657. def environ_expansions():
  1658. """Return dict of environment variables, suitable for use in string
  1659. expansions.
  1660. Every environment variable is prefixed by 'ENV_'.
  1661. """
  1662. global _environ_expansions
  1663. if _environ_expansions:
  1664. return _environ_expansions
  1665. _environ_expansions = {}
  1666. for key, value in os.environ.iteritems():
  1667. _environ_expansions['ENV_%s' % key] = value
  1668. return _environ_expansions
  1669. def make_namespec(group_name, process_name):
  1670. # we want to refer to the process by its "short name" (a process named
  1671. # process1 in the group process1 has a name "process1"). This is for
  1672. # backwards compatibility
  1673. if group_name == process_name:
  1674. name = process_name
  1675. else:
  1676. name = '%s:%s' % (group_name, process_name)
  1677. return name
  1678. def split_namespec(namespec):
  1679. names = namespec.split(':', 1)
  1680. if len(names) == 2:
  1681. # group and and process name differ
  1682. group_name, process_name = names
  1683. if not process_name or process_name == '*':
  1684. process_name = None
  1685. else:
  1686. # group name is same as process name
  1687. group_name, process_name = namespec, namespec
  1688. return group_name, process_name
  1689. # exceptions
  1690. class ProcessException(Exception):
  1691. """ Specialized exceptions used when attempting to start a process """
  1692. class NotExecutable(ProcessException):
  1693. """ Indicates that the filespec cannot be executed because its path
  1694. resolves to a file which is not executable, or which is a directory. """
  1695. class NotFound(ProcessException):
  1696. """ Indicates that the filespec cannot be executed because it could not
  1697. be found """
  1698. class NoPermission(ProcessException):
  1699. """ Indicates that the file cannot be executed because the supervisor
  1700. process does not possess the appropriate UNIX filesystem permission
  1701. to execute the file. """