Auto-generated patch by python-black
[integration/test.git] / tools / fastbgp / bgp_app_peer.py
1 """This program performs required BGP application peer operations."""
2
3 # Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
4 #
5 # This program and the accompanying materials are made available under the
6 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 # and is available at http://www.eclipse.org/legal/epl-v10.html
8
9 import requests
10 import ipaddr
11 import argparse
12 import logging
13 import time
14 import xml.dom.minidom as md
15 import os.path
16
17
18 __author__ = "Radovan Sajben"
19 __copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
20 __license__ = "Eclipse Public License v1.0"
21 __email__ = "rsajben@cisco.com"
22
23
24 def _build_url(odl_ip, port, uri):
25     """Compose URL from generic IP, port and URI fragment.
26
27     Args:
28         :param odl_ip: controller's ip address or hostname
29
30         :param port: controller's restconf port
31
32         :param uri: URI without /restconf/ to complete URL
33
34     Returns:
35         :returns url: full restconf url corresponding to params
36     """
37
38     url = "http://" + str(odl_ip) + ":" + port + "/restconf/" + uri
39     return url
40
41
42 def _stream_data(
43     xml_template, prefix_base, prefix_len, count, route_key=False, element="ipv4-routes"
44 ):
45     """Stream list of routes based on xml template. Memory non-consumable
46     data generation (on the fly).
47
48     Args:
49         :xml_template: xml template for routes
50
51         :prefix_base: first prefix IP address
52
53         :prefix_len: prefix length in bits
54
55         :count: number of routes to be generated
56
57         :route_key: bool deciding route-key tag existence
58
59         :element: element to be returned
60
61     Returns:
62         :yield xml_data: requested data by elements as xml data
63     """
64     global total_build_data_time_counter
65     if os.path.isfile(xml_template + "." + stream):
66         routes = md.parse(xml_template + "." + stream)
67     elif os.path.isfile(xml_template):
68         routes = md.parse(xml_template)
69     else:
70         logger.error("Template '{}' does not exist.".format(xml_template))
71
72     routes_node = routes.getElementsByTagName("ipv4-routes")[0]
73     route_node = routes.getElementsByTagName("ipv4-route")[0]
74     routes_node.removeChild(route_node)
75     route_prefix = route_node.getElementsByTagName("prefix")[0]
76     if route_key:
77         key = route_node.getElementsByTagName("route-key")[0]
78     prefix_gap = 2 ** (32 - prefix_len)
79     prefix_index_list = range(count)
80     if element == routes_node.tagName:
81         lines = routes_node.toxml().splitlines()
82         xml_head = lines[0] + "\n"
83         xml_tail = "\n".join(lines[1:])
84     elif element == route_node.tagName:
85         xml_head = ""
86         xml_tail = ""
87         route_node.setAttribute("xmlns", route_node.namespaceURI)
88     else:
89         prefix_index_list = range(0)
90
91     for prefix_index in prefix_index_list:
92         build_data_timestamp = time.time()
93         prefix = prefix_base + prefix_index * prefix_gap
94         prefix_str = str(prefix) + "/" + str(prefix_len)
95         route_prefix.childNodes[0].nodeValue = prefix_str
96         if route_key:
97             key.childNodes[0].nodeValue = prefix_str
98         xml_data = route_node.toxml()
99         if prefix_index == 0:
100             xml_data = xml_head + xml_data
101         if prefix_index == len(prefix_index_list) - 1:
102             xml_data = xml_data + xml_tail
103         chunk = prefix_index + 1
104         if not (chunk % 1000):
105             logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
106         else:
107             logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
108         logger.debug("xml data\n%s", xml_data)
109         total_build_data_time_counter += time.time() - build_data_timestamp
110         yield xml_data
111
112
113 def send_request(
114     operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200
115 ):
116     """Send a http request.
117
118     Args:
119         :operation: GET, POST, PUT, DELETE
120
121         :param odl_ip: controller's ip address or hostname
122
123         :param port: controller's restconf port
124
125         :param uri: URI without /restconf/ to complete URL
126
127         :param auth: authentication credentials
128
129         :param xml_data: list of routes as xml data
130
131     Returns:
132         :returns http response object
133     """
134     global total_response_time_counter
135     global total_number_of_responses_counter
136
137     ses = requests.Session()
138
139     url = _build_url(odl_ip, port, uri)
140     header = {"Content-Type": "application/xml"}
141     req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
142     prep = req.prepare()
143     try:
144         send_request_timestamp = time.time()
145         rsp = ses.send(prep, timeout=60)
146         total_response_time_counter += time.time() - send_request_timestamp
147         total_number_of_responses_counter += 1
148     except requests.exceptions.Timeout:
149         logger.error("No response from %s", odl_ip)
150     else:
151         if rsp.status_code == expect_status_code:
152             logger.debug("%s %s", rsp.request, rsp.request.url)
153             logger.debug("Request headers: %s:", rsp.request.headers)
154             logger.debug("Response: %s", rsp.text)
155             logger.debug("%s %s", rsp, rsp.reason)
156         else:
157             logger.error("%s %s", rsp.request, rsp.request.url)
158             logger.error("Request headers: %s:", rsp.request.headers)
159             logger.error("Response: %s", rsp.text)
160             logger.error("%s %s", rsp, rsp.reason)
161         return rsp
162
163
164 def get_prefixes(
165     odl_ip,
166     port,
167     uri,
168     auth,
169     prefix_base=None,
170     prefix_len=None,
171     count=None,
172     xml_template=None,
173 ):
174     """Send a http GET request for getting all prefixes.
175
176     Args:
177         :param odl_ip: controller's ip address or hostname
178
179         :param port: controller's restconf port
180
181         :param uri: URI without /restconf/ to complete URL
182
183         :param auth: authentication tupple as (user, password)
184
185         :param prefix_base: IP address of the first prefix
186
187         :prefix_len: length of the prefix in bites (specifies the increment as well)
188
189         :param count: number of prefixes to be processed
190
191         :param xml_template: xml template for building the xml data
192
193     Returns:
194         :returns None
195     """
196
197     logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
198     rsp = send_request("GET", odl_ip, port, uri, auth)
199     if rsp is not None:
200         s = rsp.text
201         s = s.replace("{", "")
202         s = s.replace("}", "")
203         s = s.replace("[", "")
204         s = s.replace("]", "")
205         prefixes = ""
206         prefix_count = 0
207         for item in s.split(","):
208             if "prefix" in item:
209                 prefixes += item + ","
210                 prefix_count += 1
211         prefixes = prefixes[: len(prefixes) - 1]
212         logger.debug("prefix_list=%s", prefixes)
213         logger.info("prefix_count=%s", prefix_count)
214
215
216 def post_prefixes(
217     odl_ip,
218     port,
219     uri,
220     auth,
221     prefix_base=None,
222     prefix_len=None,
223     count=0,
224     route_key=False,
225     xml_template=None,
226 ):
227     """Send a http POST request for creating a prefix list.
228
229     Args:
230         :param odl_ip: controller's ip address or hostname
231
232         :param port: controller's restconf port
233
234         :param uri: URI without /restconf/ to complete URL
235
236         :param auth: authentication tupple as (user, password)
237
238         :param prefix_base: IP address of the first prefix
239
240         :prefix_len: length of the prefix in bites (specifies the increment as well)
241
242         :param count: number of prefixes to be processed
243
244         :route_key: bool deciding route-key tag existence
245
246         :param xml_template: xml template for building the xml data (not used)
247
248     Returns:
249         :returns None
250     """
251     logger.info(
252         "Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
253         count,
254         prefix_base,
255         prefix_len,
256         odl_ip,
257         port,
258         uri,
259     )
260     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
261     send_request(
262         "POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204
263     )
264
265
266 def put_prefixes(
267     odl_ip,
268     port,
269     uri,
270     auth,
271     prefix_base,
272     prefix_len,
273     count,
274     route_key,
275     xml_template=None,
276 ):
277     """Send a http PUT request for updating the prefix list.
278
279     Args:
280         :param odl_ip: controller's ip address or hostname
281
282         :param port: controller's restconf port
283
284         :param uri: URI without /restconf/ to complete URL
285
286         :param auth: authentication tupple as (user, password)
287
288         :param prefix_base: IP address of the first prefix
289
290         :prefix_len: length of the prefix in bites (specifies the increment as well)
291
292         :param count: number of prefixes to be processed
293
294         :param xml_template: xml template for building the xml data (not used)
295
296     Returns:
297         :returns None
298     """
299     uri_add_prefix = uri + _uri_suffix_ipv4_routes
300     logger.info(
301         "Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
302         count,
303         prefix_base,
304         prefix_len,
305         odl_ip,
306         port,
307         uri_add_prefix,
308     )
309     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
310     send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
311
312
313 def add_prefixes(
314     odl_ip,
315     port,
316     uri,
317     auth,
318     prefix_base,
319     prefix_len,
320     count,
321     route_key,
322     xml_template=None,
323 ):
324     """Send a consequent http POST request for adding prefixes.
325
326     Args:
327         :param odl_ip: controller's ip address or hostname
328
329         :param port: controller's restconf port
330
331         :param uri: URI without /restconf/ to complete URL
332
333         :param auth: authentication tupple as (user, password)
334
335         :param prefix_base: IP address of the first prefix
336
337         :prefix_len: length of the prefix in bites (specifies the increment as well)
338
339         :param count: number of prefixes to be processed
340
341         :param xml_template: xml template for building the xml data (not used)
342
343     Returns:
344         :returns None
345     """
346     logger.info(
347         "Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
348         count,
349         prefix_base,
350         prefix_len,
351         odl_ip,
352         port,
353         uri,
354     )
355     uri_add_prefix = uri + _uri_suffix_ipv4_routes
356     prefix_gap = 2 ** (32 - prefix_len)
357     for prefix_index in range(count):
358         prefix = prefix_base + prefix_index * prefix_gap
359         logger.info(
360             "Adding prefix %s/%s to %s:%s/restconf/%s",
361             prefix,
362             prefix_len,
363             odl_ip,
364             port,
365             uri,
366         )
367         xml_stream = _stream_data(
368             xml_template, prefix, prefix_len, 1, route_key, element="ipv4-route"
369         )
370         send_request(
371             "POST",
372             odl_ip,
373             port,
374             uri_add_prefix,
375             auth,
376             xml_data=xml_stream,
377             expect_status_code=204,
378         )
379
380
381 def delete_prefixes(
382     odl_ip, port, uri, auth, prefix_base, prefix_len, count, xml_template=None
383 ):
384     """Send a http DELETE requests for deleting prefixes.
385
386     Args:
387         :param odl_ip: controller's ip address or hostname
388
389         :param port: controller's restconf port
390
391         :param uri: URI without /restconf/ to complete URL
392
393         :param auth: authentication tupple as (user, password)
394
395         :param prefix_base: IP address of the first prefix
396
397         :prefix_len: length of the prefix in bites (specifies the increment as well)
398
399         :param count: number of prefixes to be processed
400
401         :param xml_template: xml template for building the xml data (not used)
402
403     Returns:
404         :returns None
405     """
406     logger.info(
407         "Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
408         count,
409         prefix_base,
410         prefix_len,
411         odl_ip,
412         port,
413         uri,
414     )
415     partkey = "/0"
416     uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
417     prefix_gap = 2 ** (32 - prefix_len)
418     for prefix_index in range(count):
419         prefix = prefix_base + prefix_index * prefix_gap
420         logger.info(
421             "Deleting prefix %s/%s/%s from %s:%s/restconf/%s",
422             prefix,
423             prefix_len,
424             partkey,
425             odl_ip,
426             port,
427             uri,
428         )
429         send_request(
430             "DELETE",
431             odl_ip,
432             port,
433             uri_del_prefix + str(prefix) + "%2F" + str(prefix_len) + partkey,
434             auth,
435         )
436
437
438 def delete_all_prefixes(
439     odl_ip,
440     port,
441     uri,
442     auth,
443     prefix_base=None,
444     prefix_len=None,
445     count=None,
446     xml_template=None,
447 ):
448     """Send a http DELETE request for deleting all prefixes.
449
450     Args:
451         :param odl_ip: controller's ip address or hostname
452
453         :param port: controller's restconf port
454
455         :param uri: URI without /restconf/ to complete URL
456
457         :param auth: authentication tupple as (user, password)
458
459         :param prefix_base: IP address of the first prefix (not used)
460
461         :prefix_len: length of the prefix in bites (not used)
462
463         :param count: number of prefixes to be processed (not used)
464
465         :param xml_template: xml template for building the xml data (not used)
466
467     Returns:
468         :returns None
469     """
470     logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
471     uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
472     send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
473
474
475 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
476 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
477 _uri_suffix_ipv4_route = (
478     "bgp-inet:ipv4-route/"  # followed by IP address like 1.1.1.1%2F32
479 )
480
481 if __name__ == "__main__":
482     parser = argparse.ArgumentParser(description="BGP application peer script")
483     parser.add_argument(
484         "--host",
485         type=ipaddr.IPv4Address,
486         default="127.0.0.1",
487         help="ODL controller IP address",
488     )
489     parser.add_argument("--port", default="8181", help="ODL RESTCONF port")
490     parser.add_argument(
491         "--command",
492         choices=_commands,
493         metavar="command",
494         help="Command to be performed." "post, put, add, delete, delete-all, get",
495     )
496     parser.add_argument(
497         "--prefix",
498         type=ipaddr.IPv4Address,
499         default="8.0.1.0",
500         help="First prefix IP address",
501     )
502     parser.add_argument(
503         "--prefixlen", type=int, help="Prefix length in bites", default=28
504     )
505     parser.add_argument("--count", type=int, help="Number of prefixes", default=1)
506     parser.add_argument("--user", help="Restconf user name", default="admin")
507     parser.add_argument("--password", help="Restconf password", default="admin")
508     parser.add_argument(
509         "--uri",
510         help="The uri part of requests",
511         default="config/bgp-rib:application-rib/example-app-rib/"
512         "tables/bgp-types:ipv4-address-family/"
513         "bgp-types:unicast-subsequent-address-family/",
514     )
515     parser.add_argument(
516         "--xml",
517         help="File name of the xml data template",
518         default="ipv4-routes-template.xml",
519     )
520     parser.add_argument(
521         "--error",
522         dest="loglevel",
523         action="store_const",
524         const=logging.ERROR,
525         default=logging.INFO,
526         help="Set log level to error (default is info)",
527     )
528     parser.add_argument(
529         "--warning",
530         dest="loglevel",
531         action="store_const",
532         const=logging.WARNING,
533         default=logging.INFO,
534         help="Set log level to warning (default is info)",
535     )
536     parser.add_argument(
537         "--info",
538         dest="loglevel",
539         action="store_const",
540         const=logging.INFO,
541         default=logging.INFO,
542         help="Set log level to info (default is info)",
543     )
544     parser.add_argument(
545         "--debug",
546         dest="loglevel",
547         action="store_const",
548         const=logging.DEBUG,
549         default=logging.INFO,
550         help="Set log level to debug (default is info)",
551     )
552     parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
553     parser.add_argument(
554         "--stream", default="", help="ODL Stream - oxygen, fluorine ..."
555     )
556
557     args = parser.parse_args()
558
559     logger = logging.getLogger("logger")
560     log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
561     console_handler = logging.StreamHandler()
562     file_handler = logging.FileHandler(args.logfile, mode="w")
563     console_handler.setFormatter(log_formatter)
564     file_handler.setFormatter(log_formatter)
565     logger.addHandler(console_handler)
566     logger.addHandler(file_handler)
567     logger.setLevel(args.loglevel)
568
569     auth = (args.user, args.password)
570
571     odl_ip = args.host
572     port = args.port
573     command = args.command
574     prefix_base = args.prefix
575     prefix_len = args.prefixlen
576     count = args.count
577     uri = args.uri
578     stream = args.stream
579     xml_template = args.xml
580
581     # From Fluorine onward route-key argument is mandatory for identification.
582     route_key_stream = ["oxygen"]
583     route_key = True if args.stream not in route_key_stream else False
584
585     test_start_time = time.time()
586     total_build_data_time_counter = 0
587     total_response_time_counter = 0
588     total_number_of_responses_counter = 0
589
590     if command == "post":
591         post_prefixes(
592             odl_ip,
593             port,
594             uri,
595             auth,
596             prefix_base,
597             prefix_len,
598             count,
599             route_key,
600             xml_template,
601         )
602     if command == "put":
603         put_prefixes(
604             odl_ip,
605             port,
606             uri,
607             auth,
608             prefix_base,
609             prefix_len,
610             count,
611             route_key,
612             xml_template,
613         )
614     if command == "add":
615         add_prefixes(
616             odl_ip,
617             port,
618             uri,
619             auth,
620             prefix_base,
621             prefix_len,
622             count,
623             route_key,
624             xml_template,
625         )
626     elif command == "delete":
627         delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
628     elif command == "delete-all":
629         delete_all_prefixes(odl_ip, port, uri, auth)
630     elif command == "get":
631         get_prefixes(odl_ip, port, uri, auth)
632
633     total_test_execution_time = time.time() - test_start_time
634
635     logger.info("Total test execution time: %.3fs", total_test_execution_time)
636     logger.info("Total build data time: %.3fs", total_build_data_time_counter)
637     logger.info("Total response time: %.3fs", total_response_time_counter)
638     logger.info("Total number of response(s): %s", total_number_of_responses_counter)
639     file_handler.close()