summary refs log blame commit diff
path: root/synapse/app/synctl.py
blob: c04558886600c36fdc04e5f606b6d453d4a11fd2 (plain) (tree)
1
2
3
                       
                                    











                                                                          

                  
         
              
             
                 
           
 
                                                                
 
                    
                  
                 
 
 





                                                      
                      
                         
                  
                                                  
 
        
                                   

















                                                                                 
                                              



                                                                           
         
 
 
                       
                                       
                                    




                                                  
 
 
           


































                                                                      
                                      
              

                                                                        

                                                     

                   

                                    
                                
                                                    
                    
                                                              
 




























































                                                                               
 
                          
          
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import collections
import glob
import os
import os.path
import signal
import subprocess
import sys
import yaml

SYNAPSE = [sys.executable, "-B", "-m", "synapse.app.homeserver"]

GREEN = "\x1b[1;32m"
RED = "\x1b[1;31m"
NORMAL = "\x1b[m"


def write(message, colour=NORMAL, stream=sys.stdout):
    if colour == NORMAL:
        stream.write(message + "\n")
    else:
        stream.write(colour + message + NORMAL + "\n")


def start(configfile):
    write("Starting ...")
    args = SYNAPSE
    args.extend(["--daemonize", "-c", configfile])

    try:
        subprocess.check_call(args)
        write("started synapse.app.homeserver(%r)" % (configfile,), colour=GREEN)
    except subprocess.CalledProcessError as e:
        write(
            "error starting (exit code: %d); see above for logs" % e.returncode,
            colour=RED,
        )


def start_worker(app, configfile, worker_configfile):
    args = [
        "python", "-B",
        "-m", app,
        "-c", configfile,
        "-c", worker_configfile
    ]

    try:
        subprocess.check_call(args)
        write("started %s(%r)" % (app, worker_configfile), colour=GREEN)
    except subprocess.CalledProcessError as e:
        write(
            "error starting %s(%r) (exit code: %d); see above for logs" % (
                app, worker_configfile, e.returncode,
            ),
            colour=RED,
        )


def stop(pidfile, app):
    if os.path.exists(pidfile):
        pid = int(open(pidfile).read())
        os.kill(pid, signal.SIGTERM)
        write("stopped %s" % (app,), colour=GREEN)


Worker = collections.namedtuple("Worker", [
    "app", "configfile", "pidfile", "cache_factor"
])


def main():

    parser = argparse.ArgumentParser()

    parser.add_argument(
        "action",
        choices=["start", "stop", "restart"],
        help="whether to start, stop or restart the synapse",
    )
    parser.add_argument(
        "configfile",
        nargs="?",
        default="homeserver.yaml",
        help="the homeserver config file, defaults to homserver.yaml",
    )
    parser.add_argument(
        "-w", "--worker",
        metavar="WORKERCONFIG",
        help="start or stop a single worker",
    )
    parser.add_argument(
        "-a", "--all-processes",
        metavar="WORKERCONFIGDIR",
        help="start or stop all the workers in the given directory"
             " and the main synapse process",
    )

    options = parser.parse_args()

    if options.worker and options.all_processes:
        write(
            'Cannot use "--worker" with "--all-processes"',
            stream=sys.stderr
        )
        sys.exit(1)

    configfile = options.configfile

    if not os.path.exists(configfile):
        write(
            "No config file found\n"
            "To generate a config file, run '%s -c %s --generate-config"
            " --server-name=<server name>'\n" % (
                " ".join(SYNAPSE), options.configfile
            ),
            stream=sys.stderr,
        )
        sys.exit(1)

    with open(configfile) as stream:
        config = yaml.load(stream)

    pidfile = config["pid_file"]
    cache_factor = config.get("synctl_cache_factor")
    start_stop_synapse = True

    if cache_factor:
        os.environ["SYNAPSE_CACHE_FACTOR"] = str(cache_factor)

    worker_configfiles = []
    if options.worker:
        start_stop_synapse = False
        worker_configfile = options.worker
        if not os.path.exists(worker_configfile):
            write(
                "No worker config found at %r" % (worker_configfile,),
                stream=sys.stderr,
            )
            sys.exit(1)
        worker_configfiles.append(worker_configfile)

    if options.all_processes:
        worker_configdir = options.all_processes
        if not os.path.isdir(worker_configdir):
            write(
                "No worker config directory found at %r" % (worker_configdir,),
                stream=sys.stderr,
            )
            sys.exit(1)
        worker_configfiles.extend(sorted(glob.glob(
            os.path.join(worker_configdir, "*.yaml")
        )))

    workers = []
    for worker_configfile in worker_configfiles:
        with open(worker_configfile) as stream:
            worker_config = yaml.load(stream)
        worker_app = worker_config["worker_app"]
        worker_pidfile = worker_config["worker_pid_file"]
        worker_daemonize = worker_config["worker_daemonize"]
        assert worker_daemonize  # TODO print something more user friendly
        worker_cache_factor = worker_config.get("synctl_cache_factor")
        workers.append(Worker(
            worker_app, worker_configfile, worker_pidfile, worker_cache_factor,
        ))

    action = options.action

    if action == "stop" or action == "restart":
        for worker in workers:
            stop(worker.pidfile, worker.app)

        if start_stop_synapse:
            stop(pidfile, "synapse.app.homeserver")

        # TODO: Wait for synapse to actually shutdown before starting it again

    if action == "start" or action == "restart":
        if start_stop_synapse:
            start(configfile)

        for worker in workers:
            if worker.cache_factor:
                os.environ["SYNAPSE_CACHE_FACTOR"] = str(worker.cache_factor)

            start_worker(worker.app, configfile, worker.configfile)

            if cache_factor:
                os.environ["SYNAPSE_CACHE_FACTOR"] = str(cache_factor)
            else:
                os.environ.pop("SYNAPSE_CACHE_FACTOR", None)


if __name__ == "__main__":
    main()