Upgrade ietf-{inet,yang}-types to 2013-07-15
[bgpcep.git] / pcep / pcepdump / pcepdump
1 #!/usr/bin/env python2
2
3 # pcepdump - Display messages from PCCs
4 #
5 # Copyright (c) 2012,2013 Cisco Systems, Inc. and others.  All rights reserved.
6 #
7 # This program and the accompanying materials are made available under the
8 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
9 # and is available at http://www.eclipse.org/legal/epl-v10.html
10 #
11 import sys
12 import datetime
13
14 # Temporary hack
15 sys.path.append('..')
16
17 import argparse
18 import logging
19 import code
20 import pcepy.peer as _P
21 import pcepy.session as _S
22 import pcepy.message as _M
23 from pcepy.message import object as _O
24
25 _LOGGER = logging.getLogger('pcepy.peer')
26
27 DEFAULT_IP = '0.0.0.0'
28 KEEPALIVE = 30
29
30 class PcepDumper(_P.Context):
31     """A manager for a PCEP connection.
32
33     PcepDumper can be PCC or PCE and it main function it to listen in and
34     print incoming messages. It aslo provide interface for sending messages.
35     This class should not be used directly, use one of subclasses instead.
36     """
37
38     def __init__(self, config, loglevel):
39         super(PcepDumper, self).__init__(config)
40         self.logger = logging.getLogger()
41         handler = logging.StreamHandler()
42         handler.setFormatter(
43             logging.Formatter(
44                 "%(asctime)s: %(levelname)s;%(message)s",
45                 "%Y-%m-%d %H:%M:%S",
46             )
47         )
48         self.logger.addHandler(handler)
49         self.loglevel = loglevel
50         self.enabled = True
51
52     @property
53     def loglevel(self):
54         return self.logger.getEffectiveLevel()
55
56     @loglevel.setter
57     def loglevel(self, level):
58         "Set the log level of root logger"
59         self.logger.setLevel(level)
60
61     def shutdown(self):
62         self.logger.info("Bringing PCE down")
63         self.peer.shutdown()
64         self.bus.stop()
65
66     def exit(self):
67         if not self.peer:
68             return 0
69         try:
70             self.shutdown()
71             if self.peer.active:
72                 self.logger.info('Waiting on bus stop')
73                 self.bus.join()
74
75             self.logger.info("All connections are down.")
76             return 0
77         except Exception as err:
78             self.logger.exception("Clean shutdown failed: %s" % err)
79             return 1
80
81     def on_message(self, session, eventargs):
82         # Get date and time
83         dt = datetime.datetime.now()
84         # Dont print KeepAlive messages in quiet mode
85         if self.loglevel == logging.WARN and \
86             isinstance(eventargs['message'], _M.Keepalive):
87
88             return
89         else:
90             print("=======\n%s: Message on %s:\n%s\n=======" % (
91                 dt.strftime("%Y-%m-%d %H:%M:%S"), eventargs['session'],
92                 eventargs['message'].show(
93                     {
94                         'msg_sep':'\n', 'obj_sep':'\n',
95                         'item_sep':'\n', 'data':'\n'
96                     },
97                     prefix=''
98                 )
99             ))
100
101 class ManualOpener(_P.base.Opener):
102     def on_connect(self, peer, eventargs):
103         # Copy paste from peer.base.Opener, only difference is that we don't
104         # send Open message
105         session = eventargs['session']
106         if session.is_server():
107             return
108
109         # TODO: duplicate sessions -> send err 9 close
110         session[_P.base.Opener.STATE_STATE] = _P.base.Opener.OS_OPENWAIT
111         session[_P.base.Opener.STATE_PCEPTYPE] = \
112             self._get_pceptype(peer, session)
113         session[_P.base.Opener.STATE_SESSIONID] = \
114             self._get_sessionid(peer, session)
115         session[_P.base.Keeper.STATE_KEEPALIVE] = self._get_keepalive(peer, session)
116         session[_P.base.Keeper.STATE_DEADTIMER] = self._get_deadtimer(peer, session)
117         session[_P.base.Opener.STATE_OPENWAIT] = \
118             _P.base.resolve_timeout(_P.base.Opener.OPENWAIT)
119
120     def on_message(self, peer, eventargs):
121         session = eventargs['session']
122         message = eventargs['message']
123         state = session[_P.base.Opener.STATE_STATE]
124
125         is_open = isinstance(message, _M.Open)
126
127         if is_open:
128             open = message.get(_O.Open)
129             if state == _P.base.Opener.OS_OPENWAIT:
130                 if not open:
131                     _LOGGER.error('Open message without Open object received')
132                     return
133
134                 session[_P.base.Opener.STATE_REMOTE_OPEN] = open
135                 accept_open = self._accept_open(peer, session, open)
136
137                 if accept_open is True:
138                     del session[_P.base.Opener.STATE_OPENWAIT]
139                     session[_P.base.Opener.STATE_REMOTE_OK] = True
140                     session[_P.base.Opener.STATE_STATE] = \
141                         _P.base.Opener.OS_KEEPWAIT
142
143             else:
144                 _LOGGER.error('Open message received in %s state' % state)
145
146         elif isinstance(message, _M.Keepalive):
147             if state == _P.base.Opener.OS_KEEPWAIT:
148                 del session[_P.base.Opener.STATE_KEEPWAIT]
149                 self.ka_rec = True
150             elif state != _P.base.Opener.OS_UP:
151                 _LOGGER.error('Keepalive message received in %s state' % state)
152
153
154     def send_open(self, keepalive, deadtimer, session_id):
155         send(_M.Open(_O.Open(
156             keealive=keepalive,
157             deaedtimer=deadtimer,
158             session_id=session_id
159         )))
160         self.open_sended = True
161
162     def send_keepalive(self, peer, session):
163         send(_M.Keepalive())
164         self._session_open(peer, session)
165         session[_P.base.Opener.STATE_STATE] = _P.base.Opener.OS_UP
166
167
168 class PccDumper(PcepDumper):
169     """ PCC manager. See PcepDumper for more info. """
170     def __init__(self, config, loglevel, manual_open):
171         super(PccDumper, self).__init__(config, loglevel)
172         self.peer = _P.Pcc('PDE', self)
173         self.peer.add_handler(self)
174         self.role = _S.Node.ROLE_PCC
175
176         if manual_open:
177             self.peer.remove_handler(_P.base.Opener)
178             self.opener = ManualOpener()
179             self.peer.add_handler(self.opener)
180
181             def send_open(keepalive=30, deadtimer=120, session_id=1):
182                 self.opener.send_open(keepalive, deadtimer, session_id)
183
184             def send_keepalive():
185                 self.opener.send_keepalive(self.peer, self.peer.sessions[0])
186
187             self.send_open = send_open
188             self.send_keepalive = send_keepalive
189
190     def start(self, address, port):
191         self.logger.info("Starting dumper on %s:%s" % (address, port))
192         address = self.address_from(address)
193
194         local = '::' if self.address_is_ipv6(address) else '0.0.0.0'
195         self.peer_node = self.get_node(self.role, 'DumpNode',
196             self.address_from(local)
197         )
198         self.mark_node = self.get_node(self.role, 'MarkNode',
199             self.address_from(address), port
200         )
201         self.peer.create_session(self.peer_node, self.mark_node)
202
203         if not self.bus.is_alive():
204             self.bus.start()
205
206     def send_open(self, keepalive=30, deadtimer=120, session_id=1):
207         send(_M.Open(_O.Open(
208             keepalive=keepalive,
209             deadtimer=deadtimer,
210             session_id=session_id
211         )))
212         self.opened = True
213
214     def send_keepalive(self):
215         send(_M.Keepalive())
216
217 class PceDumper(PcepDumper):
218     """ PCE manager. See PcepDumper for more info. """
219     def __init__(self, config, loglevel):
220         super(PceDumper, self).__init__(config, loglevel)
221         self.peer = _P.Pce('PDE', self)
222         self.peer.add_handler(self)
223         self.role = _S.Node.ROLE_PCE
224
225     def start(self, address, port):
226         self.logger.info("Starting dumper on %s:%s" % (address, port))
227         address = self.address_from(address)
228
229         self.peer_node = self.get_node(self.role, 'DumpNode', address, port)
230         self.peer.create_server(self.peer_node)
231
232         if not self.bus.is_alive():
233             self.bus.start()
234
235 def ipaddr_port_type(data):
236     try:
237         ip, port = data.split('@')
238     except ValueError:
239         ip = data
240         port = _S.PCEP_PORT
241
242     try:
243         ip = _P.Context.address_from(ip)
244     except ValueError:
245         raise argparse.ArgumentTypeError('Invalid IP address: %s' % ip)
246
247     try:
248         port = int(port)
249     except ValueError:
250         raise argparse.ArgumentTypeError('Invalid port: %s' % port)
251
252     return ip, port
253
254 def exit():
255     sys.exit(dumper.exit())
256
257 def process_arguments(arguments):
258     """ Initialize command line arguments parser. """
259
260     parser = argparse.ArgumentParser(
261         description='Command line utility for running PCE or PCC and printing \
262             incomming messages',
263         version='%(prog)s 0.1 Copyright (c) 2012,2013 Cisco Systems, Inc. and others.  All rights reserved.',
264         usage='%(prog)s [OPTION]...',
265     )
266
267     group_type = parser.add_argument_group()
268
269     group_type.add_argument(
270         '-c', '--pcc',
271         action='store_const',
272         dest='pcc',
273         const=True,
274         default=False,
275         help='Enable PCC mode (PCE is default)',
276     )
277
278     group_parameters = parser.add_argument_group()
279
280     group_parameters.add_argument(
281         '-a', '--address',
282         action='store',
283         type=ipaddr_port_type,
284         dest='ip_port',
285         default=(_P.Context.address_from(DEFAULT_IP), _S.PCEP_PORT),
286         metavar='IP@PORT',
287         help='the ip address and port to which is this server bound'
288     )
289
290     group_parameters.add_argument(
291         '-i', '--id',
292         action='store',
293         type=int,
294         dest=_P.base.Opener.CONFIG_NODE_ID,
295         metavar='ID',
296         help='the node ID for PCE',
297     )
298
299     group_parameters.add_argument(
300         '-k', '-ka', '--keepalive',
301         action='store',
302         type=int,
303         dest=_P.base.Keeper.CONFIG_KEEPALIVE,
304         default=KEEPALIVE,
305         metavar='TIMEOUT',
306         help='in seconds, value of the desired KeepAlive timer'
307     )
308
309     group_parameters.add_argument(
310         '-d', '--deadtimer',
311         action='store',
312         type=int,
313         dest=_P.base.Keeper.CONFIG_DEADTIMER,
314         default=None,
315         metavar='TIMEOUT',
316         help='in seconds, value of the desired deadtimer'
317     )
318
319     group_open = parser.add_argument_group()
320
321     group_open.add_argument(
322         '-o', '--open',
323         action='store_const',
324         const=True,
325         dest='open',
326         default=False,
327         help='Force manual session opening (pcc only)'
328     )
329
330     group_mode = parser.add_argument_group()
331
332     # TODO: Maybe raise error when specified more of these?
333     # Now, last one specified is used.
334     group_mode.add_argument(
335         '--stateful',
336         action='store_const',
337         const=_P.base.Opener.PCEPTYPE_STATEFUL,
338         dest=_P.base.Opener.CONFIG_PCEPTYPE,
339         default=_P.base.Opener.PCEPTYPE_STATELESS,
340         help='passive stateful'
341     )
342
343     group_mode.add_argument(
344         '--active',
345         action='store_const',
346         const=_P.base.Opener.PCEPTYPE_STATEFULA,
347         dest=_P.base.Opener.CONFIG_PCEPTYPE,
348         default=_P.base.Opener.PCEPTYPE_STATELESS,
349         help='active stateful'
350     )
351
352     group_mode.add_argument(
353         '--versioned',
354         action='store',
355         dest=_P.base.Opener.CONFIG_DB_VERSION,
356         type=int,
357         default=0,
358         metavar='VERSION',
359         help='version number for versioned stateful'
360     )
361
362     group_output = parser.add_argument_group()
363
364     group_output.add_argument(
365         '--quiet',
366         action='store_const',
367         const=logging.WARN,
368         dest='loglevel',
369         default=logging.INFO,
370         help='Suppres WARN log level (INFO is default)',
371     )
372
373     group_output.add_argument(
374         '--debug',
375         action='store_const',
376         const=logging.DEBUG,
377         dest='loglevel',
378         default=logging.INFO,
379         help='Suppres DEBUG log level (INFO is default)',
380     )
381
382     # Parse arguments and create dict
383     parsed_args = vars(parser.parse_args(arguments))
384
385     # If keepalive timer was set and deadtimer was not, reset it to
386     # 4 times keepalive (max value is 255)
387     parsed_args[_P.base.Keeper.CONFIG_DEADTIMER] = \
388         parsed_args[_P.base.Keeper.CONFIG_DEADTIMER]or \
389         parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4 if \
390             parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4 < 255 else \
391             255
392
393     # Print warning, if user forces deadtimer other than keepalive * 4
394     if parsed_args[_P.base.Keeper.CONFIG_DEADTIMER] != \
395         parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4:
396         sys.stderr.write('Warrning: deadtimer (%s) should be 4 times bigger '\
397             'than keepalive (%s)\n' \
398             % (parsed_args[_P.base.Keeper.CONFIG_DEADTIMER],
399             parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE]))
400
401     # Set stateful type if versioned and stateless
402     if parsed_args[_P.base.Opener.CONFIG_DB_VERSION] and \
403         parsed_args[_P.base.Opener.CONFIG_PCEPTYPE] == \
404         _P.base.Opener.PCEPTYPE_STATELESS:
405
406         parsed_args[_P.base.Opener.CONFIG_PCEPTYPE] = \
407                 _P.base.Opener.PCEPTYPE_STATEFUL
408
409     return parsed_args
410
411 def send(msg, session=0):
412     """ UI function for messages sending. """
413     try:
414         dumper.peer.sessions[session].send(msg)
415     except(IndexError):
416         sys.stderr.write('Could not send message, ' + \
417          'sessions %s doesn\'t exists\n' % session)
418
419 def quit():
420     exit()
421
422 if __name__ == '__main__':
423     args = process_arguments(sys.argv[1:])
424
425     # Determine role (PCE or PCC)
426     role = _S.Node.ROLE_PCC if args['pcc'] else _S.Node.ROLE_PCE
427     del args['pcc']
428
429     # Log level
430     log_level = args['loglevel']
431     del args['loglevel']
432
433     manual_open = args['open']
434     del args['open']
435
436     # Additional arguments are used for config construction
437     config_session = _P.pce.Pce.CONFIG_SESSION_CONFIG if \
438         role == _S.Node.ROLE_PCE else _P.pcc.Pcc.CONFIG_SESSION_CONFIG
439
440     config = {
441         config_session:
442               { key : value for key, value in args.iteritems() if \
443                     value is not None },
444     }
445
446     # Create and start dumper
447     if role == _S.Node.ROLE_PCC:
448         dumper = PccDumper(config, log_level, manual_open)
449     else:
450         dumper = PceDumper(config, log_level)
451
452     dumper.start(args['ip_port'][0], args['ip_port'][1])
453
454     code.interact(banner=None, local=globals())
455     sys.exit(dumper.exit())
456     print "Done."