-
Notifications
You must be signed in to change notification settings - Fork 0
/
runit_helper.py
184 lines (162 loc) · 5.79 KB
/
runit_helper.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
#!/usr/bin/env python
from dateutil.parser import parse as iso2datetime
import datetime
import logging
import os
import types
from logging.handlers import (RotatingFileHandler, SysLogHandler)
from yaml import safe_load as yaml_load
FORCED_STOP_TIMEOUT = 10*60 # seconds
MAXIMUM_CRASH_HISTORY = 100
MAXIMUM_CRASHES = 5
MAXIMUM_CRASHES_PERIODE = 60 # seconds
MAXIMUM_CRASHES_DELAY = 15*60 # seconds
SERVICES_META_FILE = '/var/service/schema.yml'
SERVICES_PATH = '/service'
CRASH_LOG_PATH = '/run/runit/crashes'
SERVICELOG_MAXBYTES = 1024*1024
SERVICELOG_BACKUPS = 5
def check_crash_quota(name, current_time=None):
if current_time is None:
current_time = datetime.datetime.now()
crashes = get_recent_crashes(name, current_time)
return len(crashes) > MAXIMUM_CRASHES
def check_dependencies(name, log):
bail = False
# dependency check
for s in get_needed_services(name):
if not s.startswith('/'):
s = os.path.join(SERVICES_PATH, s)
with os.popen('sv status ' + s) as f:
output = f.readline()
output = output[:output.index(':')]
if output == 'fail':
log.error('missing dependency %s' % s)
exit(1)
elif output == 'down':
bail = True
log.debug('starting dependency %s' % s)
os.system('sv up %s' % s)
if bail: exit(0)
# make sure to run after these
for s in get_before_services(name):
if not s.startswith('/'):
s = os.path.join(SERVICES_PATH, s)
with os.popen('sv status ' + s) as f:
output = f.readline()
output = output[:output.index(':')]
if output == 'fail':
continue
elif output == 'down':
log.debug('waiting for %s' % s)
exit(0)
def get_before_services(name):
if not os.path.exists(SERVICES_META_FILE): return []
with open(SERVICES_META_FILE) as f: data = yaml_load(f)
return (s for s in data if 'after' in data[s] and name in data[s]['after'])
def get_dependant_services(name):
if not os.path.exists(SERVICES_META_FILE): return []
with open(SERVICES_META_FILE) as f: data = yaml_load(f)
return (s for s in data if 'depend' in data[s] and name in data[s]['depend'])
def get_needed_services(name):
if not os.path.exists(SERVICES_META_FILE): return []
with open(SERVICES_META_FILE) as f: data = yaml_load(f)
d = data.get(name, None)
if d is None: return []
return d.get('depend', [])
def get_logger(name, level):
if os.path.lexists('/dev/log'):
handler = SysLogHandler(address='/dev/log',
facility=SysLogHandler.LOG_DAEMON)
else:
handler = RotatingFileHandler('/var/log/messages',
maxBytes=SERVICELOG_MAXBYTES,
backupCount=SERVICELOG_BACKUPS)
formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s')
handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.addHandler(handler)
logger.setLevel(level)
return logger
def get_recent_crashes(name, current_time):
if not os.path.exists(CRASH_LOG_PATH):
os.makedirs(CRASH_LOG_PATH)
crash_log_filename = os.path.join(CRASH_LOG_PATH, name)
if os.path.exists(crash_log_filename):
with open(crash_log_filename) as f:
# normalize date string into something python can use
data = [iso2datetime(l.strip()) for l in f.readlines()]
data.sort(reverse=True)
else:
data = []
pruned_data = []
for d in data:
try:
if (current_time - d).seconds <= MAXIMUM_CRASHES_PERIODE:
pruned_data.append(d)
except OverflowError:
break
return pruned_data
def is_non_string_iterable(data):
try:
iter(data) # this should raise TypeError if not
return not isinstance(data, types.StringTypes)
except TypeError: pass
return False
def pick_one(data):
try:
iter(data) # this should raise TypeError if not
if isinstance(data, types.StringTypes):
return data
else:
return data[0]
except TypeError:
return data
def run(bin, args, user=None, group=None, redirect=True):
if redirect:
STDOUT_FILENO = 1
STDERR_FILENO = 2
os.dup2(STDOUT_FILENO, STDERR_FILENO)
binargs = []
if (user and user != 'root') or (group and group != 'root'):
if user:
u = user
else:
u = 'root'
if group:
if is_non_string_iterable(group):
u = u + ':'.join(group)
else:
u = u + ':' + group
binargs.append('/usr/bin/chpst')
binargs.append('-u')
binargs.append(u)
binargs.append(bin)
binargs += args
os.execv(binargs[0], binargs)
def update_crash_log(name):
if not os.path.exists(CRASH_LOG_PATH):
os.makedirs(CRASH_LOG_PATH)
crash_log_filename = os.path.join(CRASH_LOG_PATH, name)
if os.path.exists(crash_log_filename):
with open(crash_log_filename) as f:
data = [l.strip() for l in f.readlines()]
else:
data = []
current_time = datetime.datetime.now()
data.append(current_time.isoformat())
while len(data) > MAXIMUM_CRASH_HISTORY:
data.pop(0)
with open(crash_log_filename, 'w') as f:
for d in data:
f.write("%s\n" % d)
def stop_dependant_services(name, log):
for s in get_dependant_services(name):
if not s.startswith('/'):
s = os.path.join(SERVICES_PATH, s)
log.debug('stopping dependant %s' % s)
os.system('sv -w{time} force-stop {service}'.format(
service=s,
time=FORCED_STOP_TIMEOUT,
))
os.system('sv exit {service}'.format(service=s))