Skip to content

Commit 7685cd4

Browse files
committed
renamed utils to pymutils to avoid potential confusion in the global namespace; added verbosity options
1 parent 8be17ed commit 7685cd4

File tree

13 files changed

+92
-29
lines changed

13 files changed

+92
-29
lines changed

README.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Below is the expected structure of a pymanager file.
2727
2828
{
2929
"modules": {
30-
"http_verifier": {
30+
"pymutils.http_verifier": {
3131
"verifiers": ["HttpOkVerifier"]
3232
}
3333
},
@@ -44,7 +44,7 @@ Below is the expected structure of a pymanager file.
4444
"suppress_output": false
4545
},
4646
"verifier": {
47-
"type": "http_verifier.HttpOkVerifier",
47+
"type": "pymutils.http_verifier.HttpOkVerifier",
4848
"arguments": {
4949
"url": "http://google.com"
5050
}
@@ -98,6 +98,14 @@ Optionally, options may be passed to the process launcher. Currently recognized
9898

9999
A process may also include a verifier. If the verifier key is present, the type must be provided. The type of the verifier must be loaded in the modules section and takes the form of 'module.classname'. Optionally, you may provide a dictionary of arguments to pass to the keyword arguments of the initializer function of the verifier.
100100

101+
messages
102+
^^^^^^^^
103+
Optionally, a list of message types (strings) may appear in the configuration under the key 'messages'. This controls the verbosity of the output from the application. By default, no additional output is displayed. Currently, the following options are available:
104+
105+
* process.exit - display message when a controlled process exits, along with the return code.
106+
* verifier.fail - display messages about verifier attempts failing.
107+
* verifier.verbose - display all (debug) messages from verifier, implies all other verifier options.
108+
101109
Signal response
102110
===============
103111
The application will respond to SIGINT, SIGTERM and SIGQUIT the same way as if a DELETE / request was issued to its HTTP endpoint.

pymanager-example.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"modules": {
3-
"utils.http_verifier": {
3+
"pymutils.http_verifier": {
44
"verifiers": ["HttpOkVerifier"]
55
}
66
},
@@ -9,10 +9,10 @@
99
"executable": "python3",
1010
"arguments": ["test.py"],
1111
"options": {
12-
"suppress_output": true
12+
"suppress_output": false
1313
},
1414
"verifier": {
15-
"type": "utils.http_verifier.HttpOkVerifier",
15+
"type": "pymutils.http_verifier.HttpOkVerifier",
1616
"arguments": {
1717
"url": "http://google.com"
1818
}
@@ -23,5 +23,9 @@
2323
"enabled": true,
2424
"port": 5001
2525
},
26-
"keep_alive": true
27-
}
26+
"keep_alive": true,
27+
"messages": [
28+
"process.exit",
29+
"verifier.verbose"
30+
]
31+
}

pymanager.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import traceback
2-
from utils.process import Process
3-
import utils.verifier as verifier
2+
from pymutils.process import Process
3+
import pymutils.verifier as verifier
44
from optparse import OptionParser
5-
import utils.http_service as http_service
5+
import pymutils.http_service as http_service
6+
import collections
67
import os
78
import json
89
import inspect
910
import signal
1011
import sys
1112
import time
12-
from utils.global_storage import Globals
13+
from pymutils.global_storage import Globals
1314

1415
def parse(filename):
1516
try:
@@ -22,18 +23,29 @@ def parse(filename):
2223
print("Error while loading file {0}: {1}.".format(filename, e))
2324
exit(2)
2425
try:
25-
jdata = json.loads(config_data)
26+
jdata = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(config_data)
2627
except ValueError:
2728
print("{0} is not a valid JSON file.".format(filename))
2829
exit(3)
2930

3031
return jdata
3132

3233
def graceful_shutdown(signum, frame):
34+
if Globals.in_force_quit:
35+
return
3336
if Globals.shutdown:
37+
if signum == signal.SIGINT:
38+
Globals.in_force_quit = True
39+
for proc in Process.processes:
40+
if proc.poll() is None:
41+
proc.kill()
42+
Globals.may_terminate = True
3443
return
44+
print("Shutting down gracefully (SIGINT again to terminate immediately)...")
3545
Globals.shutdown = True
3646
for proc in Process.processes:
47+
if Globals.in_force_quit:
48+
return
3749
try:
3850
if proc.poll() is None:
3951
proc.force_terminate(Globals.terminate_time_allowed)
@@ -77,6 +89,9 @@ def main():
7789
signal.signal(signal.SIGTERM, graceful_shutdown)
7890
signal.signal(signal.SIGQUIT, graceful_shutdown)
7991

92+
if "messages" in config:
93+
Globals.messages = config["messages"]
94+
8095
try:
8196
for key, procdef in config["processes"].items():
8297
if "executable" not in procdef or "arguments" not in procdef:
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22
from .http_verifier import HttpOkVerifier
33
from .verifier import Verifier
44

5-
65
__all__ = ["exited_verifier", "http_verifier", "verifier"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ def run(self, proc):
1616
try:
1717
proc.wait(self.timeout)
1818
except subprocess.TimeoutExpired:
19+
self.log_fail("Process did not exit in given timeframe {0}s".format(self.timeout))
1920
return False
21+
if proc.returncode != self.expect_code:
22+
self.log_fail("Expected exit code {0}, got {1}".format(proc.returncode, self.expect_code))
2023
return proc.returncode == self.expect_code

pymutils/global_storage.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Globals:
2+
shutdown = False
3+
in_force_quit = False
4+
may_terminate = False
5+
terminate_time_allowed = 10
6+
7+
messages = []
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import requests
33
from requests import exceptions as reqexcept
44
from .verifier import Verifier
5+
from .global_storage import Globals
56

67
class HttpOkVerifier(Verifier):
78
def __init__(self, **kwargs):
@@ -27,9 +28,15 @@ def run(self, proc):
2728
timeout_at = time.time() + self.timeout
2829
while not passed and time.time() < timeout_at:
2930
try:
31+
self.log_verbose("Attempting connection to {0} with a timeout of {1}s".format(self.url, self.interval))
3032
resp = requests.get(self.url, headers=self.headers, timeout=self.interval)
3133
if resp.status_code >= 200 and resp.status_code < 300:
3234
passed = True
33-
except reqexcept.ConnectionError:
35+
else:
36+
self.log_fail("Got status {0} from server.".format(resp.status_code))
37+
except reqexcept.ConnectionError as e:
38+
self.log_fail("Connection error: {0}".format(e))
3439
pass
40+
if Globals.shutdown:
41+
return False
3542
return passed

utils/process.py renamed to pymutils/process.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import shlex
22
import subprocess
3+
from .global_storage import Globals
34

45
class UninitializedException(Exception):
56
def __init__(self, process):
@@ -61,7 +62,6 @@ def init(self, commandLine, verifier=None, **kwargs):
6162
if verifier is not None:
6263
if not verifier.run(self):
6364
raise VerificationFailedException(self)
64-
self.rcode = None
6565

6666
def __del__(self):
6767
if hasattr(self, "proc") and self.proc is not None:
@@ -81,8 +81,14 @@ def restart(self, timeout=10):
8181

8282
def wait(self, timeout=None):
8383
if hasattr(self, "proc") and self.proc is not None:
84+
notify = False
85+
if self.proc.returncode is None:
86+
notify = True
87+
else:
88+
return self.proc.returncode
8489
code = self.proc.wait(timeout)
85-
self.rcode = code
90+
if self.proc.returncode is not None and notify and "process.exit" in Globals.messages:
91+
print("Process {0} exited with code {1}".format(self.cmdString, self.proc.returncode))
8692
return code
8793
else:
8894
raise UninitializedException(self)
@@ -102,16 +108,19 @@ def code(self, blocking=True, timeout=None):
102108
except subprocess.TimeoutExpired:
103109
return None
104110
else:
105-
self.rcode = code
106111
return code
107112
else:
108113
raise UninitializedException(self)
109114

110115
def poll(self):
111116
if not hasattr(self, "proc") or self.proc is None:
112117
raise UninitializedException(self)
118+
notify = False
119+
if self.proc.returncode is None:
120+
notify = True
113121
self.proc.poll()
114-
self.rcode = self.proc.returncode
122+
if self.proc.returncode is not None and notify and "process.exit" in Globals.messages:
123+
print("Process {0} exited with code {1}".format(self.cmdString, self.proc.returncode))
115124
return self.proc.returncode
116125

117126
def signal(self, signal):
@@ -131,21 +140,25 @@ def pid(self):
131140

132141
def terminate(self):
133142
if hasattr(self, "proc") and self.proc is not None:
134-
self.proc.terminate()
143+
if self.proc.poll() is None:
144+
self.proc.terminate()
145+
self.proc.poll()
135146
else:
136147
raise UninitializedException(self)
137148

138149
def kill(self):
139150
if hasattr(self, "proc") and self.proc is not None:
140-
self.proc.kill()
151+
if self.proc.poll() is None:
152+
self.proc.kill()
153+
self.proc.poll()
141154
else:
142155
raise UninitializedException(self)
143156

144157
def force_terminate(self, timeout=10):
145158
if hasattr(self, "proc") and self.proc is not None:
146159
self.terminate()
147160
try:
148-
self.wait()
161+
self.wait(timeout)
149162
except subprocess.TimeoutExpired:
150163
self.kill()
151164
else:

pymutils/verifier.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from .global_storage import Globals
2+
3+
class Verifier:
4+
def run(self, proc):
5+
return True
6+
7+
def log_fail(self, message):
8+
print("pi")
9+
if "verifier.fail" in Globals.messages or "verifier.verbose" in Globals.messages:
10+
print("Verifier fail: {0}".format(message))
11+
12+
def log_verbose(self, message):
13+
if "verifier.verbose" in Globals.messages:
14+
print("Verifier: {0}".format(message))

0 commit comments

Comments
 (0)