Remove variables for genius
[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 /rests/ to complete URL
33
34     Returns:
35         :returns url: full restconf url corresponding to params
36     """
37
38     url = "http://" + str(odl_ip) + ":" + port + "/rests/" + 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.encode()
111
112
113 def send_request(
114     operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=None
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 /rests/ 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     if expect_status_code is None:
135         expect_status_code = [200]
136
137     global total_response_time_counter
138     global total_number_of_responses_counter
139
140     ses = requests.Session()
141
142     url = _build_url(odl_ip, port, uri)
143     header = {"Content-Type": "application/xml"}
144     req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
145     prep = req.prepare()
146     try:
147         send_request_timestamp = time.time()
148         rsp = ses.send(prep, timeout=60)
149         total_response_time_counter += time.time() - send_request_timestamp
150         total_number_of_responses_counter += 1
151     except requests.exceptions.Timeout:
152         logger.error("No response from %s", odl_ip)
153     else:
154         if rsp.status_code in expect_status_code:
155             logger.debug("%s %s", rsp.request, rsp.request.url)
156             logger.debug("Request headers: %s:", rsp.request.headers)
157             logger.debug("Response: %s", rsp.text)
158             logger.debug("%s %s", rsp, rsp.reason)
159         else:
160             logger.error("%s %s", rsp.request, rsp.request.url)
161             logger.error("Request headers: %s:", rsp.request.headers)
162             logger.error("Response: %s", rsp.text)
163             logger.error("%s %s", rsp, rsp.reason)
164         return rsp
165
166
167 def get_prefixes(
168     odl_ip,
169     port,
170     uri,
171     auth,
172     prefix_base=None,
173     prefix_len=None,
174     count=None,
175     xml_template=None,
176 ):
177     """Send a http GET request for getting all prefixes.
178
179     Args:
180         :param odl_ip: controller's ip address or hostname
181
182         :param port: controller's restconf port
183
184         :param uri: URI without /rests/ to complete URL
185
186         :param auth: authentication tupple as (user, password)
187
188         :param prefix_base: IP address of the first prefix
189
190         :prefix_len: length of the prefix in bites (specifies the increment as well)
191
192         :param count: number of prefixes to be processed
193
194         :param xml_template: xml template for building the xml data
195
196     Returns:
197         :returns None
198     """
199
200     logger.info("Get all prefixes from %s:%s/rests/%s", odl_ip, port, uri)
201     rsp = send_request("GET", odl_ip, port, uri, auth)
202     if rsp is not None:
203         s = rsp.text
204         s = s.replace("{", "")
205         s = s.replace("}", "")
206         s = s.replace("[", "")
207         s = s.replace("]", "")
208         prefixes = ""
209         prefix_count = 0
210         for item in s.split(","):
211             if "prefix" in item:
212                 prefixes += item + ","
213                 prefix_count += 1
214         prefixes = prefixes[: len(prefixes) - 1]
215         logger.debug("prefix_list=%s", prefixes)
216         logger.info("prefix_count=%s", prefix_count)
217
218
219 def post_prefixes(
220     odl_ip,
221     port,
222     uri,
223     auth,
224     prefix_base=None,
225     prefix_len=None,
226     count=0,
227     route_key=False,
228     xml_template=None,
229 ):
230     """Send a http POST request for creating a prefix list.
231
232     Args:
233         :param odl_ip: controller's ip address or hostname
234
235         :param port: controller's restconf port
236
237         :param uri: URI without /rests/ to complete URL
238
239         :param auth: authentication tupple as (user, password)
240
241         :param prefix_base: IP address of the first prefix
242
243         :prefix_len: length of the prefix in bites (specifies the increment as well)
244
245         :param count: number of prefixes to be processed
246
247         :route_key: bool deciding route-key tag existence
248
249         :param xml_template: xml template for building the xml data (not used)
250
251     Returns:
252         :returns None
253     """
254     logger.info(
255         "Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/rests/%s",
256         count,
257         prefix_base,
258         prefix_len,
259         odl_ip,
260         port,
261         uri,
262     )
263     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
264     send_request(
265         "POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=[201]
266     )
267
268
269 def put_prefixes(
270     odl_ip,
271     port,
272     uri,
273     auth,
274     prefix_base,
275     prefix_len,
276     count,
277     route_key,
278     xml_template=None,
279 ):
280     """Send a http PUT request for updating the prefix list.
281
282     Args:
283         :param odl_ip: controller's ip address or hostname
284
285         :param port: controller's restconf port
286
287         :param uri: URI without /rests/ to complete URL
288
289         :param auth: authentication tupple as (user, password)
290
291         :param prefix_base: IP address of the first prefix
292
293         :prefix_len: length of the prefix in bites (specifies the increment as well)
294
295         :param count: number of prefixes to be processed
296
297         :param xml_template: xml template for building the xml data (not used)
298
299     Returns:
300         :returns None
301     """
302     uri_add_prefix = f"{uri}/{_uri_suffix_ipv4_routes}"
303     logger.info(
304         "Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/rests/%s",
305         count,
306         prefix_base,
307         prefix_len,
308         odl_ip,
309         port,
310         uri_add_prefix,
311     )
312     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
313     send_request(
314         "PUT",
315         odl_ip,
316         port,
317         uri_add_prefix,
318         auth,
319         xml_data=xml_stream,
320         expect_status_code=[201, 204],
321     )
322
323
324 def add_prefixes(
325     odl_ip,
326     port,
327     uri,
328     auth,
329     prefix_base,
330     prefix_len,
331     count,
332     route_key,
333     xml_template=None,
334 ):
335     """Send a consequent http POST request for adding prefixes.
336
337     Args:
338         :param odl_ip: controller's ip address or hostname
339
340         :param port: controller's restconf port
341
342         :param uri: URI without /rests/ to complete URL
343
344         :param auth: authentication tupple as (user, password)
345
346         :param prefix_base: IP address of the first prefix
347
348         :prefix_len: length of the prefix in bites (specifies the increment as well)
349
350         :param count: number of prefixes to be processed
351
352         :param xml_template: xml template for building the xml data (not used)
353
354     Returns:
355         :returns None
356     """
357     logger.info(
358         "Add %s prefixes (starting from %s/%s) into %s:%s/rests/%s",
359         count,
360         prefix_base,
361         prefix_len,
362         odl_ip,
363         port,
364         uri,
365     )
366     uri_add_prefix = f"{uri}/{_uri_suffix_ipv4_routes}"
367     prefix_gap = 2 ** (32 - prefix_len)
368     for prefix_index in range(count):
369         prefix = prefix_base + prefix_index * prefix_gap
370         logger.info(
371             "Adding prefix %s/%s to %s:%s/rests/%s",
372             prefix,
373             prefix_len,
374             odl_ip,
375             port,
376             uri,
377         )
378         xml_stream = _stream_data(
379             xml_template, prefix, prefix_len, 1, route_key, element="ipv4-route"
380         )
381         send_request(
382             "POST",
383             odl_ip,
384             port,
385             uri_add_prefix,
386             auth,
387             xml_data=xml_stream,
388             expect_status_code=[201],
389         )
390
391
392 def delete_prefixes(
393     odl_ip, port, uri, auth, prefix_base, prefix_len, count, xml_template=None
394 ):
395     """Send a http DELETE requests for deleting prefixes.
396
397     Args:
398         :param odl_ip: controller's ip address or hostname
399
400         :param port: controller's restconf port
401
402         :param uri: URI without /rests/ to complete URL
403
404         :param auth: authentication tupple as (user, password)
405
406         :param prefix_base: IP address of the first prefix
407
408         :prefix_len: length of the prefix in bites (specifies the increment as well)
409
410         :param count: number of prefixes to be processed
411
412         :param xml_template: xml template for building the xml data (not used)
413
414     Returns:
415         :returns None
416     """
417     logger.info(
418         "Delete %s prefix(es) (starting from %s/%s) from %s:%s/rests/%s",
419         count,
420         prefix_base,
421         prefix_len,
422         odl_ip,
423         port,
424         uri,
425     )
426     partkey = ",0"
427     uri_del_prefix = f"{uri}/{_uri_suffix_ipv4_routes}/{_uri_suffix_ipv4_route}"
428     prefix_gap = 2 ** (32 - prefix_len)
429     for prefix_index in range(count):
430         prefix = prefix_base + prefix_index * prefix_gap
431         logger.info(
432             "Deleting prefix %s/%s/%s from %s:%s/rests/%s",
433             prefix,
434             prefix_len,
435             partkey,
436             odl_ip,
437             port,
438             uri,
439         )
440         send_request(
441             "DELETE",
442             odl_ip,
443             port,
444             f"{uri_del_prefix}={prefix}%2F{prefix_len}{partkey}",
445             auth,
446             expect_status_code=[204],
447         )
448
449
450 def delete_all_prefixes(
451     odl_ip,
452     port,
453     uri,
454     auth,
455     prefix_base=None,
456     prefix_len=None,
457     count=None,
458     xml_template=None,
459 ):
460     """Send a http DELETE request for deleting all prefixes.
461
462     Args:
463         :param odl_ip: controller's ip address or hostname
464
465         :param port: controller's restconf port
466
467         :param uri: URI without /rests/ to complete URL
468
469         :param auth: authentication tupple as (user, password)
470
471         :param prefix_base: IP address of the first prefix (not used)
472
473         :prefix_len: length of the prefix in bites (not used)
474
475         :param count: number of prefixes to be processed (not used)
476
477         :param xml_template: xml template for building the xml data (not used)
478
479     Returns:
480         :returns None
481     """
482     logger.info("Delete all prefixes from %s:%s/rests/%s", odl_ip, port, uri)
483     uri_del_all_prefixes = f"{uri}/{_uri_suffix_ipv4_routes}"
484     send_request(
485         "DELETE", odl_ip, port, uri_del_all_prefixes, auth, expect_status_code=[204]
486     )
487
488
489 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
490 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes"
491 _uri_suffix_ipv4_route = (
492     "bgp-inet:ipv4-route"  # followed by IP address like 1.1.1.1%2F32
493 )
494
495 if __name__ == "__main__":
496     parser = argparse.ArgumentParser(description="BGP application peer script")
497     parser.add_argument(
498         "--host",
499         type=ipaddr.IPv4Address,
500         default="127.0.0.1",
501         help="ODL controller IP address",
502     )
503     parser.add_argument("--port", default="8181", help="ODL RESTCONF port")
504     parser.add_argument(
505         "--command",
506         choices=_commands,
507         metavar="command",
508         help="Command to be performed." "post, put, add, delete, delete-all, get",
509     )
510     parser.add_argument(
511         "--prefix",
512         type=ipaddr.IPv4Address,
513         default="8.0.1.0",
514         help="First prefix IP address",
515     )
516     parser.add_argument(
517         "--prefixlen", type=int, help="Prefix length in bites", default=28
518     )
519     parser.add_argument("--count", type=int, help="Number of prefixes", default=1)
520     parser.add_argument("--user", help="Restconf user name", default="admin")
521     parser.add_argument("--password", help="Restconf password", default="admin")
522     parser.add_argument(
523         "--uri",
524         help="The uri part of requests",
525         default="data/bgp-rib:application-rib=example-app-rib/"
526         "tables=bgp-types%3Aipv4-address-family,"
527         "bgp-types%3Aunicast-subsequent-address-family",
528     )
529     parser.add_argument(
530         "--xml",
531         help="File name of the xml data template",
532         default="ipv4-routes-template.xml",
533     )
534     parser.add_argument(
535         "--error",
536         dest="loglevel",
537         action="store_const",
538         const=logging.ERROR,
539         default=logging.INFO,
540         help="Set log level to error (default is info)",
541     )
542     parser.add_argument(
543         "--warning",
544         dest="loglevel",
545         action="store_const",
546         const=logging.WARNING,
547         default=logging.INFO,
548         help="Set log level to warning (default is info)",
549     )
550     parser.add_argument(
551         "--info",
552         dest="loglevel",
553         action="store_const",
554         const=logging.INFO,
555         default=logging.INFO,
556         help="Set log level to info (default is info)",
557     )
558     parser.add_argument(
559         "--debug",
560         dest="loglevel",
561         action="store_const",
562         const=logging.DEBUG,
563         default=logging.INFO,
564         help="Set log level to debug (default is info)",
565     )
566     parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
567     parser.add_argument(
568         "--stream", default="", help="ODL Stream - oxygen, fluorine ..."
569     )
570
571     args = parser.parse_args()
572
573     logger = logging.getLogger("logger")
574     log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
575     console_handler = logging.StreamHandler()
576     file_handler = logging.FileHandler(args.logfile, mode="w")
577     console_handler.setFormatter(log_formatter)
578     file_handler.setFormatter(log_formatter)
579     logger.addHandler(console_handler)
580     logger.addHandler(file_handler)
581     logger.setLevel(args.loglevel)
582
583     auth = (args.user, args.password)
584
585     odl_ip = args.host
586     port = args.port
587     command = args.command
588     prefix_base = args.prefix
589     prefix_len = args.prefixlen
590     count = args.count
591     uri = args.uri[:-1] if len(args.uri) > 0 and args.uri[-1] == "/" else args.uri
592     stream = args.stream
593     xml_template = args.xml
594
595     # From Fluorine onward route-key argument is mandatory for identification.
596     route_key_stream = ["oxygen"]
597     route_key = True if args.stream not in route_key_stream else False
598
599     test_start_time = time.time()
600     total_build_data_time_counter = 0
601     total_response_time_counter = 0
602     total_number_of_responses_counter = 0
603
604     if command == "post":
605         post_prefixes(
606             odl_ip,
607             port,
608             uri,
609             auth,
610             prefix_base,
611             prefix_len,
612             count,
613             route_key,
614             xml_template,
615         )
616     if command == "put":
617         put_prefixes(
618             odl_ip,
619             port,
620             uri,
621             auth,
622             prefix_base,
623             prefix_len,
624             count,
625             route_key,
626             xml_template,
627         )
628     if command == "add":
629         add_prefixes(
630             odl_ip,
631             port,
632             uri,
633             auth,
634             prefix_base,
635             prefix_len,
636             count,
637             route_key,
638             xml_template,
639         )
640     elif command == "delete":
641         delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
642     elif command == "delete-all":
643         delete_all_prefixes(odl_ip, port, uri, auth)
644     elif command == "get":
645         get_prefixes(odl_ip, port, uri, auth)
646
647     total_test_execution_time = time.time() - test_start_time
648
649     logger.info("Total test execution time: %.3fs", total_test_execution_time)
650     logger.info("Total build data time: %.3fs", total_build_data_time_counter)
651     logger.info("Total response time: %.3fs", total_response_time_counter)
652     logger.info("Total number of response(s): %s", total_number_of_responses_counter)
653     file_handler.close()