Completed
Push — master ( ae553d...3df76a )
by Thomas
25:18 queued 11:15
created

lib/exabgp/application/server.py (1 issue)

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