1 """This program performs required BGP application peer operations."""
3 # Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
14 import xml.dom.minidom as md
18 __author__ = "Radovan Sajben"
19 __copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
20 __license__ = "Eclipse Public License v1.0"
21 __email__ = "rsajben@cisco.com"
24 def _build_url(odl_ip, port, uri):
25 """Compose URL from generic IP, port and URI fragment.
28 :param odl_ip: controller's ip address or hostname
30 :param port: controller's restconf port
32 :param uri: URI without /rests/ to complete URL
35 :returns url: full restconf url corresponding to params
38 url = "http://" + str(odl_ip) + ":" + port + "/rests/" + uri
43 xml_template, prefix_base, prefix_len, count, route_key=False, element="ipv4-routes"
45 """Stream list of routes based on xml template. Memory non-consumable
46 data generation (on the fly).
49 :xml_template: xml template for routes
51 :prefix_base: first prefix IP address
53 :prefix_len: prefix length in bits
55 :count: number of routes to be generated
57 :route_key: bool deciding route-key tag existence
59 :element: element to be returned
62 :yield xml_data: requested data by elements as xml data
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)
70 logger.error("Template '{}' does not exist.".format(xml_template))
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]
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:
87 route_node.setAttribute("xmlns", route_node.namespaceURI)
89 prefix_index_list = range(0)
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
97 key.childNodes[0].nodeValue = prefix_str
98 xml_data = route_node.toxml()
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)
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()
114 operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=None
116 """Send a http request.
119 :operation: GET, POST, PUT, DELETE
121 :param odl_ip: controller's ip address or hostname
123 :param port: controller's restconf port
125 :param uri: URI without /rests/ to complete URL
127 :param auth: authentication credentials
129 :param xml_data: list of routes as xml data
132 :returns http response object
134 if expect_status_code is None:
135 expect_status_code = [200]
137 global total_response_time_counter
138 global total_number_of_responses_counter
140 ses = requests.Session()
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)
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)
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)
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)
177 """Send a http GET request for getting all prefixes.
180 :param odl_ip: controller's ip address or hostname
182 :param port: controller's restconf port
184 :param uri: URI without /rests/ to complete URL
186 :param auth: authentication tupple as (user, password)
188 :param prefix_base: IP address of the first prefix
190 :prefix_len: length of the prefix in bites (specifies the increment as well)
192 :param count: number of prefixes to be processed
194 :param xml_template: xml template for building the xml data
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)
204 s = s.replace("{", "")
205 s = s.replace("}", "")
206 s = s.replace("[", "")
207 s = s.replace("]", "")
210 for item in s.split(","):
212 prefixes += item + ","
214 prefixes = prefixes[: len(prefixes) - 1]
215 logger.debug("prefix_list=%s", prefixes)
216 logger.info("prefix_count=%s", prefix_count)
230 """Send a http POST request for creating a prefix list.
233 :param odl_ip: controller's ip address or hostname
235 :param port: controller's restconf port
237 :param uri: URI without /rests/ to complete URL
239 :param auth: authentication tupple as (user, password)
241 :param prefix_base: IP address of the first prefix
243 :prefix_len: length of the prefix in bites (specifies the increment as well)
245 :param count: number of prefixes to be processed
247 :route_key: bool deciding route-key tag existence
249 :param xml_template: xml template for building the xml data (not used)
255 "Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/rests/%s",
263 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
265 "POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=[201]
280 """Send a http PUT request for updating the prefix list.
283 :param odl_ip: controller's ip address or hostname
285 :param port: controller's restconf port
287 :param uri: URI without /rests/ to complete URL
289 :param auth: authentication tupple as (user, password)
291 :param prefix_base: IP address of the first prefix
293 :prefix_len: length of the prefix in bites (specifies the increment as well)
295 :param count: number of prefixes to be processed
297 :param xml_template: xml template for building the xml data (not used)
302 uri_add_prefix = f"{uri}/{_uri_suffix_ipv4_routes}"
304 "Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/rests/%s",
312 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
320 expect_status_code=[201, 204],
335 """Send a consequent http POST request for adding prefixes.
338 :param odl_ip: controller's ip address or hostname
340 :param port: controller's restconf port
342 :param uri: URI without /rests/ to complete URL
344 :param auth: authentication tupple as (user, password)
346 :param prefix_base: IP address of the first prefix
348 :prefix_len: length of the prefix in bites (specifies the increment as well)
350 :param count: number of prefixes to be processed
352 :param xml_template: xml template for building the xml data (not used)
358 "Add %s prefixes (starting from %s/%s) into %s:%s/rests/%s",
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
371 "Adding prefix %s/%s to %s:%s/rests/%s",
378 xml_stream = _stream_data(
379 xml_template, prefix, prefix_len, 1, route_key, element="ipv4-route"
388 expect_status_code=[201],
393 odl_ip, port, uri, auth, prefix_base, prefix_len, count, xml_template=None
395 """Send a http DELETE requests for deleting prefixes.
398 :param odl_ip: controller's ip address or hostname
400 :param port: controller's restconf port
402 :param uri: URI without /rests/ to complete URL
404 :param auth: authentication tupple as (user, password)
406 :param prefix_base: IP address of the first prefix
408 :prefix_len: length of the prefix in bites (specifies the increment as well)
410 :param count: number of prefixes to be processed
412 :param xml_template: xml template for building the xml data (not used)
418 "Delete %s prefix(es) (starting from %s/%s) from %s:%s/rests/%s",
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
432 "Deleting prefix %s/%s/%s from %s:%s/rests/%s",
444 f"{uri_del_prefix}={prefix}%2F{prefix_len}{partkey}",
446 expect_status_code=[204],
450 def delete_all_prefixes(
460 """Send a http DELETE request for deleting all prefixes.
463 :param odl_ip: controller's ip address or hostname
465 :param port: controller's restconf port
467 :param uri: URI without /rests/ to complete URL
469 :param auth: authentication tupple as (user, password)
471 :param prefix_base: IP address of the first prefix (not used)
473 :prefix_len: length of the prefix in bites (not used)
475 :param count: number of prefixes to be processed (not used)
477 :param xml_template: xml template for building the xml data (not used)
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}"
485 "DELETE", odl_ip, port, uri_del_all_prefixes, auth, expect_status_code=[204]
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
495 if __name__ == "__main__":
496 parser = argparse.ArgumentParser(description="BGP application peer script")
499 type=ipaddr.IPv4Address,
501 help="ODL controller IP address",
503 parser.add_argument("--port", default="8181", help="ODL RESTCONF port")
508 help="Command to be performed." "post, put, add, delete, delete-all, get",
512 type=ipaddr.IPv4Address,
514 help="First prefix IP address",
517 "--prefixlen", type=int, help="Prefix length in bites", default=28
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")
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",
531 help="File name of the xml data template",
532 default="ipv4-routes-template.xml",
537 action="store_const",
539 default=logging.INFO,
540 help="Set log level to error (default is info)",
545 action="store_const",
546 const=logging.WARNING,
547 default=logging.INFO,
548 help="Set log level to warning (default is info)",
553 action="store_const",
555 default=logging.INFO,
556 help="Set log level to info (default is info)",
561 action="store_const",
563 default=logging.INFO,
564 help="Set log level to debug (default is info)",
566 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
568 "--stream", default="", help="ODL Stream - oxygen, fluorine ..."
571 args = parser.parse_args()
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)
583 auth = (args.user, args.password)
587 command = args.command
588 prefix_base = args.prefix
589 prefix_len = args.prefixlen
591 uri = args.uri[:-1] if len(args.uri) > 0 and args.uri[-1] == "/" else args.uri
593 xml_template = args.xml
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
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
604 if command == "post":
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)
647 total_test_execution_time = time.time() - test_start_time
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)