├── 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 |
--------------------------------------------------------------------------------