├── uwsgi_manager ├── __init__.py └── manager.py ├── setup.py └── README /uwsgi_manager/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | from setuptools import setup, find_packages 5 | 6 | def read(fname): 7 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 8 | 9 | setup( 10 | name = "uwsgi-manager", 11 | version = "0.1.1", 12 | author = "Adam Strauch", 13 | author_email = "cx@initd.cz", 14 | description = ("Python tool for controling the uWSGI instances."), 15 | license = "BSD", 16 | keywords = "uwsgi", 17 | url = "https://github.com/creckx/uWSGI-Manager", 18 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 19 | long_description="This is python tool for controling the uWSGI instances. It can read your XML configuration and stop/reload/restart/start/whatever with your uWSGI processes.",#read('README'), 20 | classifiers=[ 21 | "Development Status :: 3 - Alpha", 22 | "Topic :: Utilities", 23 | "License :: OSI Approved :: BSD License", 24 | ], 25 | install_requires=[ 26 | 27 | ], 28 | entry_points=""" 29 | [console_scripts] 30 | uwsgi-manager = uwsgi_manager.manager:main 31 | """ 32 | ) 33 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Copyright (c) Adam Strauch 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | 26 | ##################################################################### 27 | 28 | uWSGI Manager 29 | 30 | This is small python script for uWSGI instances. It can read your XML configuration and stop/reload/restart/start/what ever with your uWSGI processes. Originaly it was delevoped for Python hosting Rosti.cz, but parts of administration and tools are open now or will be opened soon. Script works only under root privileges. 31 | 32 | Using: 33 | 34 | $ mkdir /etc/uwsgi 35 | $ vim /etc/uwsgi/config.xml 36 | [copy modified configuration below] 37 | $ uwsgi-manager.py -l 38 | $ uwsgi-manager.py -R 1 39 | 40 | Config file /etc/uwsgi/config.xml: 41 | 42 | 43 | 44 | /home/user/django/ 45 | 46 | 47 | 1 48 | 0 49 | /home/user/virtualenvs/default 50 | 128 51 | 660 52 | user 53 | user 54 | /home/user/uwsgi/django.pid 55 | /home/user/uwsgi/django.sock 56 | /home/user/django/django.wsgi 57 | /home/user/uwsgi/django.log 58 | /home/user/django/ 59 | 60 | 61 | 62 | I assume, you use same paths of uwsgi binaries as I: 63 | 64 | * /usr/bin/uwsgi 65 | * /usr/local/bin/uwsgi25 66 | * /usr/local/bin/uwsgi27 67 | * /usr/local/bin/uwsgi31 68 | * /usr/local/bin/uwsgi32 69 | 70 | If you want to use another paths, you have to go into code. 71 | 72 | If you are lost, try -h: 73 | 74 | $ ./uwsgi-manager.py -h 75 | Usage: uwsgi-manager.py [options] 76 | 77 | Options: 78 | -h, --help show this help message and exit 79 | -s ID, --start=ID Start app 80 | -S ID, --stop=ID Stop app (sig 9) 81 | -r ID, --reload=ID Reload app (sig 1) 82 | -b ID, --brutal-reload=ID 83 | Brutal reload app (sig 15) 84 | -R ID, --restart=ID Restart app 85 | -c ID, --check=ID Check state of app 86 | -a, --start-all Start all apps 87 | -A, --stop-all Stop all apps 88 | -w, --reload-all Reload all apps 89 | -W, --restart-all Restart all apps 90 | -B, --brutal-reload-all 91 | Brutal reload all apps 92 | -l, --list Print state of all apps 93 | 94 | 95 | Or send me e-mail. 96 | 97 | Adam Strauch 98 | 99 | Feel free send some useful patch too :-) 100 | -------------------------------------------------------------------------------- /uwsgi_manager/manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) Adam Strauch 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without modification, 8 | # are permitted provided that the following conditions are met: 9 | # 10 | # 1. Redistributions of source code must retain the above copyright notice, 11 | # this list of conditions and the following disclaimer. 12 | # 13 | # 2. Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND 18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | import os, shlex, time, errno, sys 30 | from xml.etree.ElementTree import XMLParser 31 | from subprocess import Popen, PIPE, call 32 | from optparse import OptionParser 33 | 34 | UWSGIs_PATH = "/usr/local/bin/uwsgi" 35 | 36 | class manager: 37 | config_file = "/etc/uwsgi/config.xml" 38 | config_tree = None 39 | config = {} 40 | 41 | def __init__(self): 42 | f = open(self.config_file) 43 | xml_src = f.read() 44 | f.close() 45 | 46 | parser = XMLParser() 47 | parser.feed(xml_src) 48 | self.config_tree = parser.close() 49 | 50 | self.parse() 51 | 52 | def parse(self): 53 | for element in self.config_tree: 54 | web_id = int(element.get("id")) 55 | self.config[web_id] = {} 56 | for subelement in element: 57 | self.config[web_id][subelement.tag] = subelement.text 58 | 59 | ############################ 60 | ## Process manipulation 61 | ############################ 62 | 63 | def run_cmd(self, cmd): 64 | return_code = call(shlex.split(cmd)) 65 | 66 | if not return_code: 67 | return True 68 | else: 69 | print "Error: '%s' return %d" % (cmd, return_code) 70 | return False 71 | 72 | def send_signal(self, id, signal): 73 | if not os.path.isfile(self.config[id]["pidfile"]): 74 | return False 75 | try: 76 | os.kill(self.get_pid(id), signal) 77 | return True 78 | except OSError, err: 79 | if err.errno == errno.ESRCH: 80 | return False 81 | elif err.errno == errno.EPERM: 82 | print id 83 | print "No permission to signal this process!" 84 | sys.exit(1) 85 | else: 86 | print id 87 | print "Unknown error" 88 | sys.exit(1) 89 | else: 90 | return True 91 | 92 | 93 | def running_check(self, id): 94 | return self.send_signal(id, 0) 95 | 96 | def get_pid(self, id): 97 | f = open(self.config[id]["pidfile"]) 98 | try: 99 | pid = int(f.read().strip()) 100 | except ValueError: 101 | print "Wrong PID format (int %s)" % self.config[id]["pidfile"] 102 | sys.exit(1) 103 | f.close() 104 | return pid 105 | 106 | def check_id(self, id): 107 | if not id in self.config: 108 | print "ID not found" 109 | sys.exit(1) 110 | 111 | #{'43': {'wsgi-file': '/home/cx/co/sexflirt/sexflirt.wsgi', 'processes': '1', 'uid': 'cx', 'pythonpath': '/home/cx/co/', 'limit-as': '48', 'chmod-socket': '660', 'gid': 'cx', 'master': None, 'home': '/home/cx/virtualenvs/default', 'optimize': '1', 'socket': '/home/cx/uwsgi/sexflirt.cz.sock'}} 112 | 113 | ## Actions it selfs 114 | 115 | def start(self, id): 116 | self.check_id(id) 117 | 118 | python_bin = self.config[id]["home"]+"/bin/python -V" 119 | p = Popen(shlex.split(python_bin), stdout=PIPE, stderr=PIPE) 120 | data_raw = p.communicate() 121 | version = ".".join(data_raw[1].split(" ")[1].split(".")[0:2]) 122 | 123 | uwsgi_bin = "%s%s" % (UWSGIs_PATH, version.replace(".","")) 124 | if not os.path.isfile(uwsgi_bin): 125 | uwsgi_bin = "/usr/bin/uwsgi" 126 | 127 | if not self.running_check(id): 128 | if os.getuid() == 0: 129 | cmd = "su %s -c '%s -x %s:%d'" % (self.config[id]["uid"], uwsgi_bin, self.config_file, id) 130 | else: 131 | cmd = "%s -x %s:%d" % (uwsgi_bin, self.config_file, id) 132 | self.run_cmd(cmd) 133 | 134 | def startall(self): 135 | for web in self.config: 136 | self.start(web) 137 | 138 | def stop(self, id): 139 | self.check_id(id) 140 | if self.running_check(id): 141 | if not self.send_signal(id, 3): #QUIT signal 142 | print "Error QUIT" 143 | time.sleep(1) 144 | if self.running_check(id): 145 | if not self.send_signal(id, 9): #KILL signal 146 | print "Error KILL" 147 | time.sleep(1) 148 | else: 149 | print "Error: app %d doesn't run" % id 150 | 151 | def stopall(self): 152 | for web in self.config: 153 | self.stop(web) 154 | 155 | def restart(self, id): 156 | self.check_id(id) 157 | if self.running_check(id): 158 | self.stop(id) 159 | if not self.running_check(id): 160 | self.start(id) 161 | 162 | def restartall(self): 163 | for web in self.config: 164 | self.restart(web) 165 | 166 | def reload(self, id): 167 | self.check_id(id) 168 | if self.running_check(id): 169 | return self.send_signal(id, 1) 170 | else: 171 | self.start(id) 172 | 173 | def brutal_reload(self, id): 174 | self.check_id(id) 175 | if self.running_check(id): 176 | return self.send_signal(id, 15) 177 | else: 178 | self.start(id) 179 | 180 | def brutal_reloadall(self): 181 | for web in self.config: 182 | self.brutal_reload(web) 183 | 184 | def check(self, id): 185 | self.check_id(id) 186 | if self.running_check(id): 187 | print "Aplikace běží" 188 | else: 189 | print "Aplikace neběží" 190 | 191 | def list(self): 192 | for app in self.config: 193 | if os.getuid() != 0 and os.getlogin() != self.config[app]["uid"]: continue 194 | prefix = "run" 195 | if not self.running_check(app): 196 | prefix = "not" 197 | print "%s %d: %s (%s)" % (prefix ,app, self.config[app]["wsgi-file"], self.config[app]["uid"]) 198 | 199 | def main(): 200 | m = manager() 201 | 202 | parser = OptionParser() 203 | parser.add_option("-s", "--start", dest="start", help="Start app", metavar="ID", action="store") 204 | parser.add_option("-S", "--stop", dest="stop", help="Stop app (sig 9)", metavar="ID", action="store") 205 | parser.add_option("-r", "--reload", dest="reload", help="Reload app (sig 1)", metavar="ID", action="store") 206 | parser.add_option("-b", "--brutal-reload", dest="brutalreload", help="Brutal reload app (sig 15)", metavar="ID", action="store") 207 | parser.add_option("-R", "--restart", dest="restart", help="Restart app", metavar="ID", action="store") 208 | parser.add_option("-c", "--check", dest="check", help="Check state of app", metavar="ID", action="store") 209 | parser.add_option("-a", "--start-all", dest="startall", help="Start all apps", action="store_true") 210 | parser.add_option("-A", "--stop-all", dest="stopall", help="Stop all apps", action="store_true") 211 | parser.add_option("-w", "--reload-all", dest="reloadall", help="Reload all apps", action="store_true") 212 | parser.add_option("-W", "--restart-all", dest="restartall", help="Restart all apps", action="store_true") 213 | parser.add_option("-B", "--brutal-reload-all", dest="brutalreloadall", help="Brutal reload all apps", action="store_true") 214 | parser.add_option("-l", "--list", dest="list", help="Print state of all apps", action="store_true") 215 | 216 | 217 | (options, args) = parser.parse_args() 218 | 219 | if options.start: m.start(int(options.start)) 220 | elif options.stop: m.stop(int(options.stop)) 221 | elif options.restart: m.restart(int(options.restart)) 222 | elif options.reload: m.reload(int(options.reload)) 223 | elif options.brutalreload: m.brutal_reload(int(options.brutalreload)) 224 | elif options.check: m.check(int(options.check)) 225 | 226 | 227 | elif options.startall: m.startall() 228 | elif options.stopall: m.stopall() 229 | elif options.reloadall: m.reloadall() 230 | elif options.brutalreloadall: m.brutal_reloadall() 231 | elif options.restartall: m.restartall() 232 | elif options.list: m.list() 233 | 234 | else: parser.print_help() 235 | 236 | if __name__ == "__main__": 237 | main() 238 | --------------------------------------------------------------------------------