synctl.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Copyright 2014-2016 OpenMarket Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import argparse
  17. import collections
  18. import glob
  19. import os
  20. import os.path
  21. import signal
  22. import subprocess
  23. import sys
  24. import yaml
  25. SYNAPSE = [sys.executable, "-B", "-m", "synapse.app.homeserver"]
  26. GREEN = "\x1b[1;32m"
  27. RED = "\x1b[1;31m"
  28. NORMAL = "\x1b[m"
  29. def write(message, colour=NORMAL, stream=sys.stdout):
  30. if colour == NORMAL:
  31. stream.write(message + "\n")
  32. else:
  33. stream.write(colour + message + NORMAL + "\n")
  34. def start(configfile):
  35. write("Starting ...")
  36. args = SYNAPSE
  37. args.extend(["--daemonize", "-c", configfile])
  38. try:
  39. subprocess.check_call(args)
  40. write("started synapse.app.homeserver(%r)" % (configfile,), colour=GREEN)
  41. except subprocess.CalledProcessError as e:
  42. write(
  43. "error starting (exit code: %d); see above for logs" % e.returncode,
  44. colour=RED,
  45. )
  46. def start_worker(app, configfile, worker_configfile):
  47. args = [
  48. "python", "-B",
  49. "-m", app,
  50. "-c", configfile,
  51. "-c", worker_configfile
  52. ]
  53. try:
  54. subprocess.check_call(args)
  55. write("started %s(%r)" % (app, worker_configfile), colour=GREEN)
  56. except subprocess.CalledProcessError as e:
  57. write(
  58. "error starting %s(%r) (exit code: %d); see above for logs" % (
  59. app, worker_configfile, e.returncode,
  60. ),
  61. colour=RED,
  62. )
  63. def stop(pidfile, app):
  64. if os.path.exists(pidfile):
  65. pid = int(open(pidfile).read())
  66. os.kill(pid, signal.SIGTERM)
  67. write("stopped %s" % (app,), colour=GREEN)
  68. Worker = collections.namedtuple("Worker", [
  69. "app", "configfile", "pidfile", "cache_factor"
  70. ])
  71. def main():
  72. parser = argparse.ArgumentParser()
  73. parser.add_argument(
  74. "action",
  75. choices=["start", "stop", "restart"],
  76. help="whether to start, stop or restart the synapse",
  77. )
  78. parser.add_argument(
  79. "configfile",
  80. nargs="?",
  81. default="homeserver.yaml",
  82. help="the homeserver config file, defaults to homserver.yaml",
  83. )
  84. parser.add_argument(
  85. "-w", "--worker",
  86. metavar="WORKERCONFIG",
  87. help="start or stop a single worker",
  88. )
  89. parser.add_argument(
  90. "-a", "--all-processes",
  91. metavar="WORKERCONFIGDIR",
  92. help="start or stop all the workers in the given directory"
  93. " and the main synapse process",
  94. )
  95. options = parser.parse_args()
  96. if options.worker and options.all_processes:
  97. write(
  98. 'Cannot use "--worker" with "--all-processes"',
  99. stream=sys.stderr
  100. )
  101. sys.exit(1)
  102. configfile = options.configfile
  103. if not os.path.exists(configfile):
  104. write(
  105. "No config file found\n"
  106. "To generate a config file, run '%s -c %s --generate-config"
  107. " --server-name=<server name>'\n" % (
  108. " ".join(SYNAPSE), options.configfile
  109. ),
  110. stream=sys.stderr,
  111. )
  112. sys.exit(1)
  113. with open(configfile) as stream:
  114. config = yaml.load(stream)
  115. pidfile = config["pid_file"]
  116. cache_factor = config.get("synctl_cache_factor")
  117. start_stop_synapse = True
  118. if cache_factor:
  119. os.environ["SYNAPSE_CACHE_FACTOR"] = str(cache_factor)
  120. worker_configfiles = []
  121. if options.worker:
  122. start_stop_synapse = False
  123. worker_configfile = options.worker
  124. if not os.path.exists(worker_configfile):
  125. write(
  126. "No worker config found at %r" % (worker_configfile,),
  127. stream=sys.stderr,
  128. )
  129. sys.exit(1)
  130. worker_configfiles.append(worker_configfile)
  131. if options.all_processes:
  132. worker_configdir = options.all_processes
  133. if not os.path.isdir(worker_configdir):
  134. write(
  135. "No worker config directory found at %r" % (worker_configdir,),
  136. stream=sys.stderr,
  137. )
  138. sys.exit(1)
  139. worker_configfiles.extend(sorted(glob.glob(
  140. os.path.join(worker_configdir, "*.yaml")
  141. )))
  142. workers = []
  143. for worker_configfile in worker_configfiles:
  144. with open(worker_configfile) as stream:
  145. worker_config = yaml.load(stream)
  146. worker_app = worker_config["worker_app"]
  147. worker_pidfile = worker_config["worker_pid_file"]
  148. worker_daemonize = worker_config["worker_daemonize"]
  149. assert worker_daemonize # TODO print something more user friendly
  150. worker_cache_factor = worker_config.get("synctl_cache_factor")
  151. workers.append(Worker(
  152. worker_app, worker_configfile, worker_pidfile, worker_cache_factor,
  153. ))
  154. action = options.action
  155. if action == "stop" or action == "restart":
  156. for worker in workers:
  157. stop(worker.pidfile, worker.app)
  158. if start_stop_synapse:
  159. stop(pidfile, "synapse.app.homeserver")
  160. # TODO: Wait for synapse to actually shutdown before starting it again
  161. if action == "start" or action == "restart":
  162. if start_stop_synapse:
  163. start(configfile)
  164. for worker in workers:
  165. if worker.cache_factor:
  166. os.environ["SYNAPSE_CACHE_FACTOR"] = str(worker.cache_factor)
  167. start_worker(worker.app, configfile, worker.configfile)
  168. if cache_factor:
  169. os.environ["SYNAPSE_CACHE_FACTOR"] = str(cache_factor)
  170. else:
  171. os.environ.pop("SYNAPSE_CACHE_FACTOR", None)
  172. if __name__ == "__main__":
  173. main()