Completed
Push — master ( 8d9de4...6fdc5a )
by Thomas
10:31
created

exabgp.reactor.api.response.json   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 439
Duplicated Lines 2.51 %

Importance

Changes 0
Metric Value
eloc 309
dl 11
loc 439
rs 3.28
c 0
b 0
f 0
wmc 64

28 Methods

Rating   Name   Duplication   Size   Complexity  
A JSON._operational_counter() 0 25 1
A JSON._kv() 0 2 1
A JSON.open() 0 24 1
A JSON._json_kv() 0 2 1
A JSON._negotiated() 0 23 1
A JSON.down() 0 12 1
B JSON._string() 0 13 7
A JSON._operational_query() 0 22 1
A JSON._neighbor() 0 10 4
A JSON.negotiated() 0 7 1
A JSON.__init__() 0 4 1
A JSON._json_list() 0 2 1
A JSON._counter() 0 4 1
A JSON.packets() 0 13 2
A JSON.fsm() 0 3 1
A JSON.up() 0 3 1
A JSON._operational_advisory() 0 23 1
A JSON.notification() 0 16 1
A JSON.update() 0 6 2
A JSON.shutdown() 0 2 1
A JSON._minimalkv() 0 2 1
A JSON.keepalive() 0 2 1
A JSON.connected() 0 3 1
A JSON.refresh() 0 22 1
A JSON.signal() 0 9 1
F JSON._update() 0 52 18
A JSON.operational() 11 11 4
A JSON._header() 0 15 5

1 Function

Rating   Name   Duplication   Size   Complexity  
A nop() 0 2 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like exabgp.reactor.api.response.json 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
#!/usr/bin/env python
2
# encoding: utf-8
3
"""
4
Response/json.py
5
6
Created by Thomas Mangin on 2012-12-30.
7
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
8
License: 3-clause BSD. (See the COPYRIGHT file)
9
"""
10
11
import os
12
import socket
13
import sys
14
import time
15
import signal
16
17
from exabgp.vendoring import six
18
19
from exabgp.util import hexstring
20
21
from exabgp.bgp.message import Message
22
from exabgp.bgp.message import IN
23
24
from exabgp.configuration.environment import environment
25
from exabgp.bgp.message.open.capability.refresh import REFRESH
26
27
28
if sys.version_info > (3,):
29
    long = int
30
31
SIGNAL_NAME = dict(
32
    (k, v) for v, k in reversed(sorted(signal.__dict__.items())) if v.startswith('SIG') and not v.startswith('SIG_')
33
)
34
35
36
def nop(_):
37
    return _
38
39
40
class JSON(object):
41
    _count = {}
42
43
    def __init__(self, version):
44
        self.version = version
45
        self.time = nop
46
        self.compact = environment.settings().api.compact
47
48
    # def _reset (self, neighbor):
49
    # 	self._count[neighbor.uid] = 0
50
    # 	return 0
51
52
    def _counter(self, neighbor):
53
        increased = self._count.get(neighbor.uid, 0) + 1
54
        self._count[neighbor.uid] = increased
55
        return increased
56
57
    def _string(self, object):
58
        if issubclass(object.__class__, bool):
59
            return 'true' if object else 'false'
60
        if issubclass(object.__class__, long):
0 ignored issues
show
introduced by
The variable long does not seem to be defined in case sys.version_info > TupleNode on line 28 is False. Are you sure this can never be the case?
Loading history...
61
            return '%s' % object
62
        if issubclass(object.__class__, int):
63
            return '%s' % object
64
        string = '%s' % object
65
        if '{' in string:
66
            return string
67
        if '[' in string:
68
            return string
69
        return '"%s"' % object
70
71
    def _header(self, content, header, body, neighbor, message_type=None):
72
        peer = '"host" : "%s", ' % socket.gethostname()
73
        pid = '"pid" : %s, ' % os.getpid()
74
        ppid = '"ppid" : %s, ' % os.getppid()
75
        counter = '"counter": %s, ' % self._counter(neighbor) if neighbor is not None else ''
76
        header = '"header": "%s", ' % hexstring(header) if header else ''
77
        body = '"body": "%s", ' % hexstring(body) if body else ''
78
        mtype = '"type": "%s", ' % message_type if message_type else 'default'
79
80
        return (
81
            '{ '
82
            '"exabgp": "%s", '
83
            '"time": %s, '
84
            '%s%s%s%s%s%s%s%s '
85
            '}' % (self.version, self.time(time.time()), peer, pid, ppid, counter, mtype, header, body, content)
86
        )
87
88
    __neighbor = '''\
89
"neighbor": {
90
	"address": { "local": "%s", "peer": "%s" },
91
	"asn": { "local": %s, "peer": %s }
92
	%s%s%s%s
93
}'''.replace(
94
        '\t', ''
95
    ).replace(
96
        '\n', ' '
97
    )
98
99
    def _neighbor(self, neighbor, direction, content):
100
        return self.__neighbor % (
101
            neighbor.local_address,
102
            neighbor.peer_address,
103
            neighbor.local_as,
104
            neighbor.peer_as,
105
            ', ' if direction else '',
106
            '"direction": "%s"' % direction if direction else '',
107
            ', ' if content else ' ',
108
            content,
109
        )
110
111
    def _kv(self, extra):
112
        return ", ".join('"%s": %s' % (k, self._string(v)) for (k, v) in six.iteritems(extra))
113
114
    def _json_kv(self, extra):
115
        return ", ".join('"%s": %s' % (k, v.json()) for (k, v) in six.iteritems(extra))
116
117
    def _json_list(self, extra):
118
        return ", ".join('%s' % (v.json()) for v in six.iteritems(extra))
119
120
    def _minimalkv(self, extra):
121
        return ", ".join('"%s": %s' % (k, self._string(v)) for (k, v) in six.iteritems(extra) if v)
122
123
    def up(self, neighbor):
124
        return self._header(
125
            self._neighbor(neighbor, None, self._kv({'state': 'up',})), '', '', neighbor, message_type='state'
126
        )
127
128
    def connected(self, neighbor):
129
        return self._header(
130
            self._neighbor(neighbor, None, self._kv({'state': 'connected',})), '', '', neighbor, message_type='state'
131
        )
132
133
    def down(self, neighbor, reason=''):
134
        def escape_quote(reason):
135
            # the {} and [] change is an horrible hack until we generate python objects
136
            # as otherwise we interpret the string as a list or dict
137
            return reason.replace('[', '(').replace(']', ')').replace('{', '(').replace('}', ')').replace('"', '\\"')
138
139
        return self._header(
140
            self._neighbor(neighbor, None, self._kv({'state': 'down', 'reason': escape_quote(reason),})),
141
            '',
142
            '',
143
            neighbor,
144
            message_type='state',
145
        )
146
147
    def shutdown(self):
148
        return self._header(self._kv({'notification': 'shutdown',}), '', '', None, message_type='notification')
149
150
    def _negotiated(self, negotiated):
151
        return {
152
            'negotiated': '{ %s } '
153
            % self._kv(
154
                {
155
                    'message_size': negotiated.msg_size,
156
                    'hold_time': negotiated.holdtime,
157
                    'asn4': negotiated.asn4,
158
                    'multisession': negotiated.multisession,
159
                    'operational': negotiated.operational,
160
                    'refresh': REFRESH.json(negotiated.refresh),
161
                    'families': '[ %s ]' % ' ,'.join(['"%s %s"' % family for family in negotiated.families]),
162
                    'nexthop': '[ %s ]' % ' ,'.join(['"%s %s %s"' % family for family in negotiated.nexthop]),
163
                    'add_path': '{ "send": %s, "receive": %s }'
164
                    % (
165
                        '[ %s ]'
166
                        % ', '.join([family for family in negotiated.families if negotiated.addpath.send(*family)]),
167
                        '[ %s ]'
168
                        % ', '.join(
169
                            [
170
                                '"%s %s"' % family
171
                                for family in negotiated.families
172
                                if negotiated.addpath.receive(*family)
173
                            ]
174
                        ),
175
                    ),
176
                }
177
            )
178
        }
179
180
    def negotiated(self, neighbor, negotiated):
181
        return self._header(
182
            self._neighbor(neighbor, None, self._kv(self._negotiated(negotiated))),
183
            '',
184
            '',
185
            neighbor,
186
            message_type='negotiated',
187
        )
188
189
    def fsm(self, neighbor, fsm):
190
        return self._header(
191
            self._neighbor(neighbor, None, self._kv({'state': fsm.name()})), '', '', neighbor, message_type='fsm'
192
        )
193
194
    def signal(self, neighbor, signal):
195
        return self._header(
196
            self._neighbor(
197
                neighbor, None, self._kv({'code': '%d' % signal, 'name': SIGNAL_NAME.get(signal, 'UNKNOWN'),})
198
            ),
199
            '',
200
            '',
201
            neighbor,
202
            message_type='signal',
203
        )
204
205
    def notification(self, neighbor, direction, message, negotiated, header, body):
206
        return self._header(
207
            self._neighbor(
208
                neighbor,
209
                direction,
210
                self._kv(
211
                    {
212
                        'notification': '{ %s } '
213
                        % self._kv({'code': message.code, 'subcode': message.subcode, 'data': hexstring(message.data),})
214
                    }
215
                ),
216
            ),
217
            header,
218
            body,
219
            neighbor,
220
            message_type='notification',
221
        )
222
223
    def packets(self, neighbor, direction, category, negotiated, header, body):
224
        message = {
225
            'message': '{ %s } '
226
            % self._kv({'category': category, 'header': hexstring(header), 'body': hexstring(body),})
227
        }
228
        if negotiated:
229
            message.update(self._negotiated(negotiated))
230
        return self._header(
231
            self._neighbor(neighbor, direction, self._kv(message)),
232
            '',
233
            '',
234
            neighbor,
235
            message_type=Message.string(category),
236
        )
237
238
    def keepalive(self, neighbor, direction, negotiated, header, body):
239
        return self._header(self._neighbor(neighbor, direction, ''), header, body, neighbor, message_type='keepalive')
240
241
    def open(self, neighbor, direction, message, negotiated, header, body):
242
        return self._header(
243
            self._neighbor(
244
                neighbor,
245
                direction,
246
                self._kv(
247
                    {
248
                        'open': '{ %s }'
249
                        % self._kv(
250
                            {
251
                                'version': message.version,
252
                                'asn': message.asn,
253
                                'hold_time': message.hold_time,
254
                                'router_id': message.router_id,
255
                                'capabilities': '{ %s }' % self._json_kv(message.capabilities),
256
                            }
257
                        )
258
                    }
259
                ),
260
            ),
261
            header,
262
            body,
263
            neighbor,
264
            message_type='open',
265
        )
266
267
    def _update(self, update):
268
        plus = {}
269
        minus = {}
270
271
        # all the next-hops should be the same but let's not assume it
272
273
        for nlri in update.nlris:
274
            nexthop = str(nlri.nexthop) if nlri.nexthop else 'null'
275
            if nlri.action == IN.ANNOUNCED:  # pylint: disable=E1101
276
                plus.setdefault(nlri.family(), {}).setdefault(nexthop, []).append(nlri)
277
            if nlri.action == IN.WITHDRAWN:  # pylint: disable=E1101
278
                minus.setdefault(nlri.family(), []).append(nlri)
279
280
        add = []
281
        for family in plus:
282
            s = '"%s %s": { ' % family
283
            m = ''
284
            for nexthop in plus[family]:
285
                nlris = plus[family][nexthop]
286
                m += '"%s": [ ' % nexthop
287
                m += ', '.join('%s' % nlri.json(compact=self.compact) for nlri in nlris)
288
                m += ' ], '
289
            s += m[:-2]
290
            s += ' }'
291
            add.append(s)
292
293
        remove = []
294
        for family in minus:
295
            nlris = minus[family]
296
            s = '"%s %s": [ ' % family
297
            s += ', '.join('%s' % nlri.json(compact=self.compact) for nlri in nlris)
298
            s += ' ]'
299
            remove.append(s)
300
301
        nlri = ''
302
        if not add and not remove:
303
            if update.nlris:  # an EOR
304
                return {'message': '{ %s }' % update.nlris[0].json()}
305
        if add:
306
            nlri += '"announce": { %s }' % ', '.join(add)
307
        if add and remove:
308
            nlri += ', '
309
        if remove:
310
            nlri += '"withdraw": { %s }' % ', '.join(remove)
311
312
        attributes = '' if not update.attributes else '"attribute": { %s }' % update.attributes.json()
313
        if not attributes or not nlri:
314
            update = '"update": { %s%s }' % (attributes, nlri)
315
        else:
316
            update = '"update": { %s, %s }' % (attributes, nlri)
317
318
        return {'message': '{ %s }' % update}
319
320
    def update(self, neighbor, direction, update, negotiated, header, body):
321
        message = self._update(update)
322
        if negotiated:
323
            message.update(self._negotiated(negotiated))
324
        return self._header(
325
            self._neighbor(neighbor, direction, self._kv(message)), header, body, neighbor, message_type='update'
326
        )
327
328
    def refresh(self, neighbor, direction, refresh, negotiated, header, body):
329
        return self._header(
330
            self._neighbor(
331
                neighbor,
332
                direction,
333
                self._kv(
334
                    {
335
                        'route-refresh': '{ %s }'
336
                        % self._kv(
337
                            {
338
                                'afi': '"%s"' % refresh.afi,
339
                                'safi': '"%s"' % refresh.safi,
340
                                'subtype': '"%s"' % refresh.reserved,
341
                            }
342
                        )
343
                    }
344
                ),
345
            ),
346
            header,
347
            body,
348
            neighbor,
349
            message_type='refresh',
350
        )
351
352
    def _operational_query(self, neighbor, direction, operational, header, body):
353
        return self._header(
354
            self._neighbor(
355
                neighbor,
356
                direction,
357
                self._kv(
358
                    {
359
                        'operational': '{ %s }'
360
                        % self._kv(
361
                            {
362
                                'name': '"%s"' % operational.name,
363
                                'afi': '"%s"' % operational.afi,
364
                                'safi': '"%s"' % operational.safi,
365
                            }
366
                        )
367
                    }
368
                ),
369
            ),
370
            header,
371
            body,
372
            neighbor,
373
            message_type='operational',
374
        )
375
376
    def _operational_advisory(self, neighbor, direction, operational, header, body):
377
        return self._header(
378
            self._neighbor(
379
                neighbor,
380
                direction,
381
                self._kv(
382
                    {
383
                        'operational': '{ %s }'
384
                        % self._kv(
385
                            {
386
                                'name': '"%s"' % operational.name,
387
                                'afi': '"%s"' % operational.afi,
388
                                'safi': '"%s"' % operational.safi,
389
                                'advisory': '"%s"' % operational.data,
390
                            }
391
                        )
392
                    }
393
                ),
394
            ),
395
            header,
396
            body,
397
            neighbor,
398
            message_type='operational',
399
        )
400
401
    def _operational_counter(self, neighbor, direction, operational, header, body):
402
        return self._header(
403
            self._neighbor(
404
                neighbor,
405
                direction,
406
                self._kv(
407
                    {
408
                        'operational': '{ %s }'
409
                        % self._kv(
410
                            {
411
                                'name': '"%s"' % operational.name,
412
                                'afi': '"%s"' % operational.afi,
413
                                'safi': '"%s"' % operational.safi,
414
                                'router-id': operational.routerid,
415
                                'sequence': operational.sequence,
416
                                'counter': operational.counter,
417
                            }
418
                        )
419
                    }
420
                ),
421
            ),
422
            header,
423
            body,
424
            neighbor,
425
            message_type='operational',
426
        )
427
428 View Code Duplication
    def operational(self, neighbor, direction, what, operational, negotiated, header, body):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
429
        if what == 'advisory':
430
            return self._operational_advisory(neighbor, direction, operational, header, body)
431
        elif what == 'query':
432
            return self._operational_query(neighbor, direction, operational, header, body)
433
        elif what == 'counter':
434
            return self._operational_counter(neighbor, direction, operational, header, body)
435
        # elif what == 'interface':
436
        # 	return self._operational_interface(peer,operational)
437
        else:
438
            raise RuntimeError('the code is broken, we are trying to print a unknown type of operational message')
439