Completed
Push — master ( ea37f3...9aacac )
by Thomas
11:37
created

exabgp.application.server._delayed_signal()   A

Complexity

Conditions 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nop 2
dl 0
loc 16
rs 9.85
c 0
b 0
f 0
1
# encoding: utf-8
2
3
"""exabgp server"""
4
5
6
import os
7
import sys
8
import time
9
import signal
10
import syslog
11
import argparse
12
import platform
13
14
from exabgp.debug import trace_interceptor
15
16
# import before the fork to improve copy on write memory savings
17
from exabgp.reactor.loop import Reactor
18
19
from exabgp.util.dns import warn
20
from exabgp.logger import log
21
22
# this is imported from configuration.setup to make sure it was initialised
23
from exabgp.environment import getenv
24
from exabgp.environment import getconf
25
from exabgp.environment import ENVFILE
26
from exabgp.environment import ROOT
27
28
from exabgp.application.pipe import named_pipe
29
from exabgp.version import version
30
31
from exabgp.bgp.message.update.attribute import Attribute
32
33
34
def __exit(memory, code):
35
    if memory:
36
        from exabgp.vendoring import objgraph
37
38
        sys.stdout.write('memory utilisation\n\n')
39
        sys.stdout.write(objgraph.show_most_common_types(limit=20))
40
        sys.stdout.write('\n\n\n')
41
        sys.stdout.write('generating memory utilisation graph\n\n')
42
        sys.stdout.write()
43
        obj = objgraph.by_type('Reactor')
44
        objgraph.show_backrefs([obj], max_depth=10)
45
    sys.exit(code)
46
47
48
def _delayed_signal(delay, signalnum):
49
    if not delay:
50
        return
51
52
    pid = os.fork()
53
    if pid:
54
        # the parent process is the one waiting
55
        # and sending a signa to the child
56
        try:
57
            time.sleep(delay)
58
            os.kill(pid, signalnum)
59
        finally:
60
            try:
61
                pid, code = os.wait()
62
            finally:
63
                sys.exit(code)
64
65
66
def args(sub):
67
    # fmt:off
68
    sub.add_argument('-t', '--test', help='perform a configuration validity check only', action='store_true')
69
    sub.add_argument('-d', '--debug', help='start the python debugger on serious logging and on SIGTERM (shortcut for exabgp.log.all=true exabgp.log.level=DEBUG)', action='store_true')
70
    sub.add_argument('-s', '--signal', help='issue a SIGUSR1 to reload the configuration after <time> seconds, only useful for code debugging', type=int)
71
    sub.add_argument('-v', '--validate', help='validate the configuration file format only', action='store_true')
72
    sub.add_argument('-1', '--once', help='only perform one attempt to connect to peers', action='store_true')
73
    sub.add_argument('-p', '--pdb', help='fire the debugger on critical logging, SIGTERM, and exceptions (shortcut for exabgp.pdb.enable=true)', action='store_true')
74
    sub.add_argument('-m', '--memory', help='display memory usage information on exit', action='store_true')
75
    sub.add_argument('--profile', help='enable profiling and set where the information should be saved', type=str, default='')
76
    sub.add_argument('configuration', help='configuration file(s)', nargs='+', type=str)
77
    # fmt:on
78
79
80
def cmdline(cmdarg):
81
    if not os.path.isfile(ENVFILE):
82
        comment = 'environment file missing\ngenerate it using "exabgp env --fi > %s"' % ENVFILE
83
    else:
84
        comment = ''
85
86
    env = getenv()
87
    # Must be done before setting the logger as it modify its behaviour
88
    if cmdarg.debug:
89
        env.log.all = True
90
        env.log.level = syslog.LOG_DEBUG
91
92
    log.init()
93
94
    if cmdarg.profile:
95
        env.profile.enable = True
96
        env.profile.file = cmdarg.profile
97
98
    if cmdarg.once:
99
        env.tcp.once = True
100
101
    if cmdarg.pdb:
102
        env.debug.pdb = True
103
104
    if cmdarg.test:
105
        env.log.parser = True
106
107
    if cmdarg.memory:
108
        env.debug.memory = True
109
110
    if env.cache.attributes:
111
        Attribute.caching = env.cache.attributes
112
113
    configurations = []
114
    for configuration in cmdarg.configuration:
115
        location = getconf(configuration)
116
        if not location:
117
            log.critical(f'{configuration} is not an exabgp config file', 'configuration')
118
            sys.exit(1)
119
        configurations.append(configuration)
120
121
    delay = cmdarg.signal
122
    _delayed_signal(delay, signal.SIGUSR1)
123
124
    if env.debug.rotate or len(configurations) == 1:
125
        run(comment, configurations, cmdarg.validate)
126
127
    if not (env.log.destination in ('syslog', 'stdout', 'stderr') or env.log.destination.startswith('host:')):
128
        log.error('can not log to files when running multiple configuration (as we fork)', 'configuration')
129
        sys.exit(1)
130
131
    try:
132
        # run each configuration in its own process
133
        pids = []
134
        for configuration in configurations:
135
            pid = os.fork()
136
            if pid == 0:
137
                run(comment, [configuration], cmdarg.validate, os.getpid())
138
            else:
139
                pids.append(pid)
140
141
        # If we get a ^C / SIGTERM, ignore just continue waiting for our child process
142
        signal.signal(signal.SIGINT, signal.SIG_IGN)
143
144
        # wait for the forked processes
145
        for pid in pids:
146
            os.waitpid(pid, 0)
147
    except OSError as exc:
148
        log.critical('can not fork, errno %d : %s' % (exc.errno, exc.strerror), 'reactor')
149
        sys.exit(1)
150
151
152
def run(comment, configurations, validate, pid=0):
153
    env = getenv()
154
155
    log.notice('Thank you for using ExaBGP', 'welcome')
156
    log.notice('%s' % version, 'version')
157
    log.notice('%s' % sys.version.replace('\n', ' '), 'interpreter')
158
    log.notice('%s' % ' '.join(platform.uname()[:5]), 'os')
159
    log.notice('%s' % ROOT, 'installation')
160
161
    if comment:
162
        log.notice(comment, 'advice')
163
164
    warning = warn()
165
    if warning:
166
        log.warning(warning, 'advice')
167
168
    if env.api.cli:
169
        pipename = 'exabgp' if env.api.pipename is None else env.api.pipename
170
        pipes = named_pipe(ROOT, pipename)
171
        if len(pipes) != 1:
172
            env.api.cli = False
173
            log.error(
174
                'could not find the named pipes (%s.in and %s.out) required for the cli' % (pipename, pipename), 'cli'
175
            )
176
            log.error('we scanned the following folders (the number is your PID):', 'cli')
177
            for location in pipes:
178
                log.error(' - %s' % location, 'cli control')
179
            log.error('please make them in one of the folder with the following commands:', 'cli control')
180
            log.error('> mkfifo %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
181
            log.error('> chmod 600 %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
182
            if os.getuid() != 0:
183
                log.error(
184
                    '> chown %d:%d %s/run/%s.{in,out}' % (os.getuid(), os.getgid(), os.getcwd(), pipename),
185
                    'cli control',
186
                )
187
        else:
188
            pipe = pipes[0]
189
            os.environ['exabgp_cli_pipe'] = pipe
190
            os.environ['exabgp_api_pipename'] = pipename
191
192
            log.info('named pipes for the cli are:', 'cli control')
193
            log.info('to send commands  %s%s.in' % (pipe, pipename), 'cli control')
194
            log.info('to read responses %s%s.out' % (pipe, pipename), 'cli control')
195
196
    if not env.profile.enable:
197
        exit_code = Reactor(configurations).run(validate, ROOT)
198
        __exit(env.debug.memory, exit_code)
199
200
    try:
201
        import cProfile as profile
202
    except ImportError:
203
        import profile
204
205
    if env.profile.file == 'stdout':
206
        profiled = 'Reactor(%s).run(%s,"%s")' % (str(configurations), str(validate), str(ROOT))
207
        exit_code = profile.run(profiled)
208
        __exit(env.debug.memory, exit_code)
209
210
    if pid:
211
        profile_name = "%s-pid-%d" % (env.profile.file, pid)
212
    else:
213
        profile_name = env.profile.file
214
215
    notice = ''
216
    if os.path.isdir(profile_name):
217
        notice = 'profile can not use this filename as output, it is not a directory (%s)' % profile_name
218
    if os.path.exists(profile_name):
219
        notice = 'profile can not use this filename as output, it already exists (%s)' % profile_name
220
221
    if not notice:
222
        cwd = os.getcwd()
223
        log.debug('profiling ....', 'reactor')
224
        profiler = profile.Profile()
225
        profiler.enable()
226
        try:
227
            exit_code = Reactor(configurations).run(validate, ROOT)
228
        except Exception:
229
            exit_code = Reactor.Exit.unknown
230
            raise
231
        finally:
232
            from exabgp.vendoring import lsprofcalltree
233
234
            profiler.disable()
235
            kprofile = lsprofcalltree.KCacheGrind(profiler)
236
            try:
237
                destination = profile_name if profile_name.startswith('/') else os.path.join(cwd, profile_name)
238
                with open(destination, 'w+') as write:
239
                    kprofile.output(write)
240
            except IOError:
241
                notice = 'could not save profiling in formation at: ' + destination
242
                log.debug("-" * len(notice), 'reactor')
243
                log.debug(notice, 'reactor')
244
                log.debug("-" * len(notice), 'reactor')
245
            __exit(env.debug.memory, exit_code)
246
    else:
247
        log.debug("-" * len(notice), 'reactor')
248
        log.debug(notice, 'reactor')
249
        log.debug("-" * len(notice), 'reactor')
250
        Reactor(configurations).run(validate, ROOT)
251
        __exit(env.debug.memory, 1)
252
253
254
def main():
255
    parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
256
    args(parser)
257
    trace_interceptor()
258
    cmdline(parser, parser.parse_args())
259
260
261
if __name__ == '__main__':
262
    main()
263