-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpymanager.py
265 lines (235 loc) · 8.06 KB
/
pymanager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
from pymutils.process import Process
import pymutils.verifier as verifier
from optparse import OptionParser
import pymutils.http_service as http_service
from pymutils.debug import verbose, debug, log
import collections
import os
import json
import inspect
import signal
import sys
import time
from pymutils.global_storage import Globals
version = "0.2.6.1"
__version__ = version
def parse(filename):
try:
with open(filename, 'r') as f:
config_data = f.read()
except FileNotFoundError:
print("Cannot find file {0}.".format(filename))
exit(1)
except Exception as e:
print("Error while loading file {0}: {1}.".format(filename, e))
exit(2)
try:
jdata = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(config_data)
except ValueError:
print("{0} is not a valid JSON file.".format(filename))
exit(3)
return jdata
def clean_outfile():
if Globals.outfile is not sys.stdout and Globals.outfile is not None:
Globals.outfile.close()
Globals.outfile = None
def graceful_shutdown(signum, frame):
if Globals.in_force_quit:
return
if Globals.shutdown:
if signum == signal.SIGINT:
Globals.in_force_quit = True
Globals.status = "force shutdown"
for proc in Process.processes:
if proc.poll() is None:
proc.kill()
Globals.may_terminate = True
clean_outfile()
return
print("Shutting down gracefully (SIGINT again to terminate immediately)...")
Globals.shutdown = True
Globals.status = "shutdown"
for proc in Process.processes:
if Globals.in_force_quit:
return
try:
if proc.poll() is None:
proc.force_terminate(Globals.terminate_time_allowed)
except Exception:
pass
Globals.may_terminate = True
clean_outfile()
def spawnDaemon(func, conf):
try:
pid = os.fork()
if pid > 0:
return
except OSError as e:
print("fork #1 failed: {0} ({1})".format(e.errno, e.strerror))
sys.exit(6)
os.setsid()
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
print("fork #2 failed: {0} ({1})".format(e.errno, e.strerror))
sys.exit(7)
func(conf)
os._exit(os.EX_OK)
def main():
parser = OptionParser()
parser.add_option("-V", "--version", dest="version", default=False, action="store_true", help="Display version and exit.")
parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="Display process launch and verification step-by-step.")
parser.add_option("-w", "--debug", dest="debug", default=False, action="store_true", help="Display debug information. Implies verbose.")
parser.add_option("-f", "--file", dest="filename", default="pymanager.json", help="The name of the pymanager file to use, defaults to pymanager.json.", metavar="FILE")
parser.add_option("-d", "--daemon", dest="daemon", default=False, action="store_true", help="Daemonize self after processes are launched.")
parser.add_option("-l", "--logfile", dest="logfile", default=None, help="Send all messages to this logfile instead of output.")
opts, args = parser.parse_args()
if opts.version:
print("pymanager version {0}".format(version))
exit(0)
config = parse(opts.filename)
if opts.debug:
config["verbose"] = 2
elif opts.verbose:
config["verbose"] = 1
else:
config["verbose"] = 0
if opts.logfile != None:
config["logfile"] = opts.logfile
if opts.daemon:
spawnDaemon(spawn_and_monitor, config)
return 0
else:
return spawn_and_monitor(config)
def spawn_and_monitor(config):
verifiers = {}
if "verbose" in config:
Globals.verbose = config["verbose"]
if "logfile" in config:
Globals.outfile = open(config["logfile"], "wb", 0)
else:
Globals.outfile = sys.stdout
verbose("Checking HTTP configuration.")
if "http" in config:
hconf = config["http"]
debug("HTTP is present.")
if "enabled" in hconf and hconf["enabled"]:
debug("HTTP is enabled.")
port = 5001
if "port" in hconf:
debug("Port is present in configuration.")
port = hconf["port"]
http_service.fork_http_service(port)
verbose("HTTP service listening on port :{0}".format(port))
else:
debug("HTTP is disabled.")
if "default_shell" in config:
debug("Default shell is present, value: {0}".format(config["default_shell"]))
Globals.default_shell = config["default_shell"]
verbose("Parsing modules list.")
Globals.status = "parsing modules"
if "modules" in config:
for module, definition in config["modules"].items():
debug("Loading module {0}".format(module))
if "verifiers" not in definition:
log("[WARNING] module {0} does not contain a list of verifiers to load.".format(module))
else:
try:
mod = __import__(module)
for v in definition["verifiers"]:
try:
a = getattr(mod, v)
if inspect.isclass(a):
if issubclass(a, verifier.Verifier):
debug("Loading verifier {0}".format(v))
verifiers["{0}.{1}".format(module, v)] = getattr(mod, v)
else:
log("[WARNING] object '{0}' from module {1} is not a subclass of Verifier".format(v, module))
else:
log("[WARNING] object '{0}' from module {1} is not a class".format(v, module))
except AttributeError:
log("[WARNING] missing verifier '{0}' from module {1}".format(v, module))
except ImportError:
log("[WARNING] module {0} not found.".format(module))
verbose("Modules are loaded, parsing processes.")
if not "processes" in config:
log("[ERROR] No processes listed in the configuration file.")
clean_outfile()
return 4
signal.signal(signal.SIGINT, graceful_shutdown)
signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGQUIT, graceful_shutdown)
verbose("Processes parsed, launching.")
Globals.status = "launching processes"
if "messages" in config:
Globals.messages = config["messages"]
try:
for key, procdef in config["processes"].items():
verbose("Launching process key '{0}'.".format(key))
if "executable" not in procdef or "arguments" not in procdef:
raise KeyError("Missing executable or arguments in definition for process {0}.".format(key))
cmdargs = [procdef["executable"]]
cmdargs += procdef["arguments"]
vfy = None
if "verifier" in procdef:
if "type" not in procdef["verifier"]:
raise KeyError("Missing verifier type for verifier of process {0}.".format(key))
if procdef["verifier"]["type"] not in verifiers:
raise ValueError("Missing verifier {0} used in process {1}".format(procdef["verifier"]["type"], key))
args = {}
if "arguments" in procdef["verifier"]:
args = procdef["verifier"]["arguments"]
debug("Setting up verifier {0} for process.".format(procdef["verifier"]["type"]))
vfy = verifiers[procdef["verifier"]["type"]](**args)
options = {}
if "options" in procdef:
options = procdef["options"]
verbose("Creating process.")
proc = Process(cmdargs, vfy, **options)
Process.add_process(proc)
verbose("Process creation finished.")
except Exception as e:
etype, _, _ = sys.exc_info()
log("[ERROR] could not set up processes: {0}: {1}".format(etype.__name__, e))
Globals.status = "shutdown"
#traceback.print_exc()
for proc in Process.processes:
try:
proc.kill()
except Exception:
pass
clean_outfile()
return 5
verbose("Finished setting up processes.")
if "keep_alive" in config:
if config["keep_alive"]:
Globals.keep_alive = True
if "graceful_time" in config:
try:
t = int(config["graceful_time"])
if t < 0:
raise ValueError
Globals.terminate_time_allowed = t
except ValueError:
log("[WARNING] invalid graceful_time '{0}', must be a positive number.".format(t))
Globals.status = "running"
runningProcesses = len(Process.processes)
while (runningProcesses or Globals.keep_alive) and not Globals.shutdown:
runningProcesses = 0
for proc in Process.processes:
result = proc.poll()
if result is None:
runningProcesses += 1
time.sleep(5)
if not Globals.keep_alive and not runningProcesses:
Globals.may_terminate = True
verbose("Entering shutdown phase.")
Globals.status = "shutdown"
while not Globals.may_terminate:
time.sleep(5)
clean_outfile()
return 0
if __name__ == "__main__":
exit(main())