Test Failed
Push — master ( db4166...efa4d0 )
by Thomas
11:36
created

exabgp.application.server   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 277
rs 8.48
c 0
b 0
f 0
wmc 49

5 Functions

Rating   Name   Duplication   Size   Complexity  
A args() 0 11 1
A main() 0 5 1
F cmdline() 0 108 26
F run() 0 98 19
A __exit() 0 12 2

How to fix   Complexity   

Complexity

Complex classes like exabgp.application.server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 setup_report
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.control 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
    # Must be done before setting the logger as it modify its behaviour
65
    if cmdarg.debug:
66
        env = getenv()
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
0 ignored issues
show
introduced by
The variable env does not seem to be defined in case cmdarg.debug on line 65 is False. Are you sure this can never be the case?
Loading history...
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(env, 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(env, 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(env, comment, configurations, validate, pid=0):
169
    log.notice('Thank you for using ExaBGP', 'welcome')
170
    log.notice('%s' % version, 'version')
171
    log.notice('%s' % sys.version.replace('\n', ' '), 'interpreter')
172
    log.notice('%s' % ' '.join(platform.uname()[:5]), 'os')
173
    log.notice('%s' % ROOT, 'installation')
174
175
    if comment:
176
        log.notice(comment, 'advice')
177
178
    warning = warn()
179
    if warning:
180
        log.warning(warning, 'advice')
181
182
    if env.api.cli:
183
        pipename = 'exabgp' if env.api.pipename is None else env.api.pipename
184
        pipes = named_pipe(ROOT, pipename)
185
        if len(pipes) != 1:
186
            env.api.cli = False
187
            log.error(
188
                'could not find the named pipes (%s.in and %s.out) required for the cli' % (pipename, pipename), 'cli'
189
            )
190
            log.error('we scanned the following folders (the number is your PID):', 'cli')
191
            for location in pipes:
192
                log.error(' - %s' % location, 'cli control')
193
            log.error('please make them in one of the folder with the following commands:', 'cli control')
194
            log.error('> mkfifo %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
195
            log.error('> chmod 600 %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
196
            if os.getuid() != 0:
197
                log.error(
198
                    '> chown %d:%d %s/run/%s.{in,out}' % (os.getuid(), os.getgid(), os.getcwd(), pipename),
199
                    'cli control',
200
                )
201
        else:
202
            pipe = pipes[0]
203
            os.environ['exabgp_cli_pipe'] = pipe
204
            os.environ['exabgp_api_pipename'] = pipename
205
206
            log.info('named pipes for the cli are:', 'cli control')
207
            log.info('to send commands  %s%s.in' % (pipe, pipename), 'cli control')
208
            log.info('to read responses %s%s.out' % (pipe, pipename), 'cli control')
209
210
    if not env.profile.enable:
211
        exit_code = Reactor(configurations).run(validate, ROOT)
212
        __exit(env.debug.memory, exit_code)
213
214
    try:
215
        import cProfile as profile
216
    except ImportError:
217
        import profile
218
219
    if env.profile.file == 'stdout':
220
        profiled = 'Reactor(%s).run(%s,"%s")' % (str(configurations), str(validate), str(ROOT))
221
        exit_code = profile.run(profiled)
222
        __exit(env.debug.memory, exit_code)
223
224
    if pid:
225
        profile_name = "%s-pid-%d" % (env.profile.file, pid)
226
    else:
227
        profile_name = env.profile.file
228
229
    notice = ''
230
    if os.path.isdir(profile_name):
231
        notice = 'profile can not use this filename as output, it is not a directory (%s)' % profile_name
232
    if os.path.exists(profile_name):
233
        notice = 'profile can not use this filename as output, it already exists (%s)' % profile_name
234
235
    if not notice:
236
        cwd = os.getcwd()
237
        log.debug('profiling ....', 'reactor')
238
        profiler = profile.Profile()
239
        profiler.enable()
240
        try:
241
            exit_code = Reactor(configurations).run(validate, ROOT)
242
        except Exception:
243
            exit_code = Reactor.Exit.unknown
244
            raise
245
        finally:
246
            from exabgp.vendoring import lsprofcalltree
247
248
            profiler.disable()
249
            kprofile = lsprofcalltree.KCacheGrind(profiler)
250
            try:
251
                destination = profile_name if profile_name.startswith('/') else os.path.join(cwd, profile_name)
252
                with open(destination, 'w+') as write:
253
                    kprofile.output(write)
254
            except IOError:
255
                notice = 'could not save profiling in formation at: ' + destination
256
                log.debug("-" * len(notice), 'reactor')
257
                log.debug(notice, 'reactor')
258
                log.debug("-" * len(notice), 'reactor')
259
            __exit(env.debug.memory, exit_code)
260
    else:
261
        log.debug("-" * len(notice), 'reactor')
262
        log.debug(notice, 'reactor')
263
        log.debug("-" * len(notice), 'reactor')
264
        Reactor(configurations).run(validate, ROOT)
265
        __exit(env.debug.memory, 1)
266
267
268
def main():
269
    parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
270
    args(parser)
271
    setup_report()
272
    cmdline(parser, parser.parse_args())
273
274
275
if __name__ == '__main__':
276
    main()
277