-
Notifications
You must be signed in to change notification settings - Fork 10
/
almalinux_build_node.py
executable file
·188 lines (161 loc) · 6.11 KB
/
almalinux_build_node.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
#!/usr/bin/env python3
import argparse
import logging
import os
import signal
import sys
import time
from threading import Event
import sentry_sdk
import build_node.build_node_globals as node_globals
from build_node.build_node_builder import BuildNodeBuilder
from build_node.build_node_config import BuildNodeConfig
from build_node.build_node_errors import BuildError, BuildExcluded
from build_node.build_node_supervisor import BuilderSupervisor
from build_node.mock.mock_environment import MockError
from build_node.utils.file_utils import chown_recursive, rm_sudo
from build_node.utils.config import locate_config_file
from build_node.utils.spec_parser import SpecParseError
running = True
def init_args_parser():
"""
Build node command line arguments parser initialization.
Returns
-------
argparse.ArgumentParser
"""
parser = argparse.ArgumentParser(
prog='castor_build_node',
description='CloudLinux Build System build node'
)
parser.add_argument('-c', '--config', help='configuration file path')
parser.add_argument('-i', '--id', help='build node unique identifier')
parser.add_argument('-m', '--master', help='build server connection URL')
parser.add_argument('-t', '--threads', type=int,
help='build threads count')
parser.add_argument('-w', '--working-dir', help='working directory path')
parser.add_argument('-v', '--verbose', action='store_true',
help='enable additional debug output')
return parser
def init_logger(verbose):
"""
Initializes a program root logger.
Parameters
----------
verbose : bool
Enable DEBUG messages output if True, print only INFO and higher
otherwise.
"""
level = logging.DEBUG if verbose else logging.INFO
handler = logging.StreamHandler()
handler.setLevel(level)
log_format = "%(asctime)s %(levelname)-8s [%(threadName)s]: %(message)s"
formatter = logging.Formatter(log_format, '%y.%m.%d %H:%M:%S')
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(level)
def init_working_dir(config):
"""
The working directory initialization function. It removes files from
previous executions and creates the necessary directories.
Parameters
----------
config : BuildNodeConfig
"""
working_dir = config.working_dir
if os.path.exists(working_dir):
# delete builder directories from a previous execution
chown_recursive(working_dir)
for name in os.listdir(working_dir):
if name.startswith('builder-'):
# in some cases users have weird permissions on their
# files / directories and even chown_recursive can't help us
# to delete them from current user, so the use sudo.
# e.g.:
# $ mkdir test
# $ touch test/test.txt
# $ chmod 444 test
# $ rm -rf test/
# rm: cannot remove 'test/test.txt': Permission denied
rm_sudo(os.path.join(working_dir, name))
else:
logging.debug('creating the {0} working directory'.
format(config.working_dir))
os.makedirs(config.working_dir, 0o750)
if not os.path.exists(config.mock_configs_storage_dir):
logging.debug('creating the {0} mock configuration files directory'.
format(config.mock_configs_storage_dir))
os.makedirs(config.mock_configs_storage_dir, 0o750)
def init_sentry(config):
"""
Initializes Sentry if dsn parameter is provided.
Parameters
----------
config : BuildNodeConfig
"""
if not config.sentry_dsn:
return
sentry_sdk.init(
dsn=config.sentry_dsn,
traces_sample_rate=config.sentry_traces_sample_rate,
environment=config.sentry_environment,
ignore_errors=[MockError, BuildError, BuildExcluded, SpecParseError],
)
def main(sys_args):
args_parser = init_args_parser()
args = args_parser.parse_args(sys_args)
try:
config_file = locate_config_file('build_node', args.config)
config = BuildNodeConfig(config_file, master_url=args.master,
node_id=args.id, threads_count=args.threads)
except ValueError as e:
args_parser.error('Configuration error: {0}'.format(e))
return 2
init_logger(args.verbose)
init_working_dir(config)
init_sentry(config)
node_terminated = Event()
node_graceful_terminated = Event()
def signal_handler(signum, frame):
global running
running = False
logging.info('terminating build node: {0} received'.format(signum))
node_terminated.set()
node_graceful_terminated.set()
def sigusr_handler(signum, frame):
global running
running = False
logging.info('terminating build node: {0} received'.format(signum))
node_terminated.set()
node_graceful_terminated.set()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGUSR1, sigusr_handler)
node_globals.init_supervisors(config)
builders = []
for i in range(0, config.threads_count):
builder = BuildNodeBuilder(config, i, node_terminated,
node_graceful_terminated)
builders.append(builder)
builder.start()
builder_supervisor = BuilderSupervisor(config, builders, node_terminated)
builder_supervisor.start()
global running
exit_code = 0
while running:
time.sleep(10)
threads = builders + [builder_supervisor]
if all([t.is_alive() for t in threads]):
continue
if (all([not b.is_alive() for b in builders])
or not builder_supervisor.is_alive()):
logging.error('All builders are dead, exiting')
running = False
exit_code = 1
for b in builders:
b.join(0.1)
builder_supervisor.join(1.)
return exit_code
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))