Completed
Push — master ( 0f3e04...1c5edf )
by Thomas
30:23 queued 15:11
created

exabgp.application.cli.setargs()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# encoding: utf-8
3
4
"""exabgp command line interface"""
5
6
7
import os
8
import sys
9
import time
10
import errno
11
import select
12
import signal
13
import argparse
14
15
from exabgp.application.pipe import named_pipe
16
from exabgp.application.pipe import check_fifo
17
18
from exabgp.reactor.network.error import error
19
from exabgp.reactor.api.response.answer import Answer
20
21
from exabgp.environment import getenv
22
from exabgp.environment import ROOT
23
24
25
errno_block = set(
26
    (
27
        errno.EINPROGRESS,
28
        errno.EALREADY,
29
        errno.EAGAIN,
30
        errno.EWOULDBLOCK,
31
        errno.EINTR,
32
        errno.EDEADLK,
33
        errno.EBUSY,
34
        errno.ENOBUFS,
35
        errno.ENOMEM,
36
    )
37
)
38
39
40
class AnswerStream:
41
    done = '\n%s\n' % Answer.done
42
    error = '\n%s\n' % Answer.error
43
    shutdown = '\n%s\n' % Answer.error
44
    buffer_size = Answer.buffer_size + 2
45
46
47
def open_reader(recv):
48
    def open_timeout(signum, frame):
49
        sys.stderr.write('could not connect to read response from ExaBGP\n')
50
        sys.stderr.flush()
51
        sys.exit(1)
52
53
    signal.signal(signal.SIGALRM, open_timeout)
54
    signal.alarm(5)
55
56
    done = False
57
    while not done:
58
        try:
59
            reader = os.open(recv, os.O_RDONLY | os.O_NONBLOCK)
60
            done = True
61
        except IOError as exc:
62
            if exc.args[0] in errno_block:
63
                signal.signal(signal.SIGALRM, open_timeout)
64
                signal.alarm(5)
65
                continue
66
            sys.stdout.write('could not read answer from ExaBGP')
67
            sys.stdout.flush()
68
            sys.exit(1)
69
    signal.alarm(0)
70
    return reader
0 ignored issues
show
introduced by
The variable reader does not seem to be defined in case the while loop on line 57 is not entered. Are you sure this can never be the case?
Loading history...
71
72
73
def open_writer(send):
74
    def write_timeout(signum, frame):
75
        sys.stderr.write('could not send command to ExaBGP (command timeout)')
76
        sys.stderr.flush()
77
        sys.exit(1)
78
79
    signal.signal(signal.SIGALRM, write_timeout)
80
    signal.alarm(5)
81
82
    try:
83
        writer = os.open(send, os.O_WRONLY)
84
    except OSError as exc:
85
        if exc.errno == errno.ENXIO:
86
            sys.stdout.write('ExaBGP is not running / using the configured named pipe')
87
            sys.stdout.flush()
88
            sys.exit(1)
89
        sys.stdout.write('could not communicate with ExaBGP')
90
        sys.stdout.flush()
91
        sys.exit(1)
92
    except IOError as exc:
93
        sys.stdout.write('could not communicate with ExaBGP')
94
        sys.stdout.flush()
95
        sys.exit(1)
96
97
    signal.alarm(0)
98
    return writer
99
100
101
def setargs(sub):
102
    # fmt:off
103
    sub.add_argument('command', nargs='*', help='command to run on the router')
104
    # fmt:on
105
106
107
def main():
108
    parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
109
    setargs(parser)
110
    cmdline(parser.parse_args())
111
112
113
def cmdline(cmdarg):
114
    pipename = getenv().api.pipename
115
116
    command = cmdarg.command
117
118
    pipes = named_pipe(ROOT, pipename)
119
    if len(pipes) != 1:
120
        sys.stdout.write('could not find ExaBGP\'s named pipes (%s.in and %s.out) for the cli\n' % (pipename, pipename))
121
        sys.stdout.write('we scanned the following folders (the number is your PID):\n - ')
122
        sys.stdout.write('\n - '.join(pipes))
123
        sys.stdout.flush()
124
        sys.exit(1)
125
126
    send = pipes[0] + pipename + '.in'
127
    recv = pipes[0] + pipename + '.out'
128
129
    if not check_fifo(send):
130
        sys.stdout.write('could not find write named pipe to connect to ExaBGP')
131
        sys.stdout.flush()
132
        sys.exit(1)
133
134
    if not check_fifo(recv):
135
        sys.stdout.write('could not find read named pipe to connect to ExaBGP')
136
        sys.stdout.flush()
137
        sys.exit(1)
138
139
    reader = open_reader(recv)
140
141
    rbuffer = b''
142
    start = time.time()
143
    while True:
144
        try:
145
            while select.select([reader], [], [], 0) != ([], [], []):
146
                rbuffer += os.read(reader, 4096)
147
                rbuffer = rbuffer[-AnswerStream.buffer_size :]
148
        except IOError as exc:
149
            if exc.errno in error.block:
150
                continue
151
            sys.stdout.write('could not clear named pipe from potential previous command data (%s)' % str(exc))
152
            sys.stdout.flush()
153
            sys.exit(1)
154
        except OSError as exc:
155
            if exc.errno in error.block:
156
                continue
157
            sys.stdout.write('could not clear named pipe from potential previous command data (%s)' % str(exc))
158
            sys.stdout.write(exc)
159
            sys.stdout.flush()
160
            sys.exit(1)
161
162
        # we are not ack'ing the command and probably have read all there is
163
        if time.time() > start + 1.5:
164
            break
165
166
        # we read nothing, nothing to do
167
        if not rbuffer:
168
            break
169
170
        # we read some data but it is not ending by a new line (ie: not a command completion)
171
        if rbuffer[-1] != 10:  # \n
172
            continue
173
        if AnswerStream.done.endswith(rbuffer[-len(AnswerStream.done) :]):
174
            break
175
        if AnswerStream.error.endswith(rbuffer[-len(AnswerStream.error) :]):
176
            break
177
        if AnswerStream.shutdown.endswith(rbuffer[-len(AnswerStream.shutdown) :]):
178
            break
179
180
    renamed = ['']
181
182
    for pos, token in enumerate(command):
183
        for nickname, name, match in (
184
            ('a', 'announce', lambda pos, pre: pos == 0 or pre.count('.') == 3 or pre.count(':') != 0),
185
            ('a', 'attributes', lambda pos, pre: pre[-1] == 'announce' or pre[-1] == 'withdraw'),
186
            ('c', 'configuration', lambda pos, pre: True),
187
            ('e', 'eor', lambda pos, pre: pre[-1] == 'announce'),
188
            ('e', 'extensive', lambda _, pre: 'show' in pre),
189
            ('f', 'flow', lambda pos, pre: pre[-1] == 'announce' or pre[-1] == 'withdraw'),
190
            ('f', 'flush', lambda pos, pre: pos == 0 or pre.count('.') == 3 or pre.count(':') != 0),
191
            ('h', 'help', lambda pos, pre: pos == 0),
192
            ('i', 'in', lambda pos, pre: pre[-1] == 'adj-rib'),
193
            ('n', 'neighbor', lambda pos, pre: pos == 0 or pre[-1] == 'show'),
194
            ('r', 'route', lambda pos, pre: pre == 'announce' or pre == 'withdraw'),
195
            ('rr', 'route-refresh', lambda _, pre: pre == 'announce'),
196
            ('s', 'show', lambda pos, pre: pos == 0),
197
            ('t', 'teardown', lambda pos, pre: pos == 0 or pre.count('.') == 3 or pre.count(':') != 0),
198
            ('s', 'summary', lambda pos, pre: pos != 0),
199
            ('v', 'vps', lambda pos, pre: pre[-1] == 'announce' or pre[-1] == 'withdraw'),
200
            ('o', 'operation', lambda pos, pre: pre[-1] == 'announce'),
201
            ('o', 'out', lambda pos, pre: pre[-1] == 'adj-rib'),
202
            ('a', 'adj-rib', lambda pos, pre: pre[-1] in ['clear', 'flush', 'show']),
203
            ('w', 'withdraw', lambda pos, pre: pos == 0 or pre.count('.') == 3 or pre.count(':') != 0),
204
            ('w', 'watchdog', lambda pos, pre: pre[-1] == 'announce' or pre[-1] == 'withdraw'),
205
            ('neighbour', 'neighbor', lambda pos, pre: True),
206
            ('neigbour', 'neighbor', lambda pos, pre: True),
207
            ('neigbor', 'neighbor', lambda pos, pre: True),
208
        ):
209
            if (token == nickname or name.startswith(token)) and match(pos, renamed):
210
                renamed.append(name)
211
                break
212
        else:
213
            renamed.append(token)
214
215
    sending = ' '.join(renamed).strip()
216
217
    # This does not change the behaviour for well formed command
218
    if sending != command:
219
        print('command: %s' % sending)
220
221
    writer = open_writer(send)
222
    try:
223
        os.write(writer, sending.encode('utf-8') + b'\n')
224
        os.close(writer)
225
    except IOError as exc:
226
        sys.stdout.write('could not send command to ExaBGP (%s)' % str(exc))
227
        sys.stdout.flush()
228
        sys.exit(1)
229
    except OSError as exc:
230
        sys.stdout.write('could not send command to ExaBGP (%s)' % str(exc))
231
        sys.stdout.flush()
232
        sys.exit(1)
233
234
    if command == 'reset':
235
        sys.exit(0)
236
237
    waited = 0.0
238
    buf = b''
239
    done = False
240
    done_time_diff = 0.5
241
    while not done:
242
        try:
243
            r, _, _ = select.select([reader], [], [], 0.01)
244
        except OSError as exc:
245
            if exc.errno in error.block:
246
                continue
247
            sys.stdout.write('could not get answer from ExaBGP (%s)' % str(exc))
248
            sys.stdout.flush()
249
            sys.exit(1)
250
        except IOError as exc:
251
            if exc.errno in error.block:
252
                continue
253
            sys.stdout.write('could not get answer from ExaBGP (%s)' % str(exc))
254
            sys.stdout.flush()
255
            sys.exit(1)
256
257
        if waited > 5.0:
258
            sys.stderr.write('\n')
259
            sys.stderr.write('warning: no end of command message received\n')
260
            sys.stderr.write(
261
                'warning: normal if exabgp.api.ack is set to false otherwise some data may get stuck on the pipe\n'
262
            )
263
            sys.stderr.write('warning: otherwise it may cause exabgp reactor to block\n')
264
            sys.exit(0)
265
        elif not r:
266
            waited += 0.01
267
            continue
268
        else:
269
            waited = 0.0
270
271
        try:
272
            raw = os.read(reader, 4096)
273
        except OSError as exc:
274
            if exc.errno in error.block:
275
                continue
276
            sys.stdout.write('could not read answer from ExaBGP (%s)' % str(exc))
277
            sys.stdout.flush()
278
            sys.exit(1)
279
        except IOError as exc:
280
            if exc.errno in error.block:
281
                continue
282
            sys.stdout.write('could not read answer from ExaBGP (%s)' % str(exc))
283
            sys.stdout.flush()
284
            sys.exit(1)
285
286
        buf += raw
287
        while b'\n' in buf:
288
            line, buf = buf.split(b'\n', 1)
289
            string = line.decode()
290
            if string == Answer.done:
291
                done = True
292
                break
293
            if string == Answer.shutdown:
294
                sys.stderr.write('ExaBGP is shutting down, command aborted\n')
295
                sys.stderr.flush()
296
                done = True
297
                break
298
            if string == Answer.error:
299
                done = True
300
                sys.stderr.write('ExaBGP returns an error (see ExaBGP\'s logs for more information)\n')
301
                sys.stderr.write('use help for a list of available commands\n')
302
                sys.stderr.flush()
303
                break
304
            sys.stdout.write('%s\n' % string)
305
            sys.stdout.flush()
306
307
        if not getenv().api.ack and not raw.decode():
308
            this_moment = time.time()
309
            recv_epoch_time = os.path.getmtime(recv)
310
            time_diff = this_moment - recv_epoch_time
311
            if time_diff >= done_time_diff:
312
                done = True
313
314
    try:
315
        os.close(reader)
316
    except Exception:
317
        pass
318
319
    sys.exit(0)
320
321
322
if __name__ == '__main__':
323
    try:
324
        main()
325
    except KeyboardInterrupt:
326
        pass
327