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 /restconf/ to complete URL
35 :returns url: full restconf url corresponding to params
38 url = "http://" + str(odl_ip) + ":" + port + "/restconf/" + 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
114 operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200
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 /restconf/ to complete URL
127 :param auth: authentication credentials
129 :param xml_data: list of routes as xml data
132 :returns http response object
134 global total_response_time_counter
135 global total_number_of_responses_counter
137 ses = requests.Session()
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)
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)
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)
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)
174 """Send a http GET request for getting all prefixes.
177 :param odl_ip: controller's ip address or hostname
179 :param port: controller's restconf port
181 :param uri: URI without /restconf/ to complete URL
183 :param auth: authentication tupple as (user, password)
185 :param prefix_base: IP address of the first prefix
187 :prefix_len: length of the prefix in bites (specifies the increment as well)
189 :param count: number of prefixes to be processed
191 :param xml_template: xml template for building the xml data
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)
201 s = s.replace("{", "")
202 s = s.replace("}", "")
203 s = s.replace("[", "")
204 s = s.replace("]", "")
207 for item in s.split(","):
209 prefixes += item + ","
211 prefixes = prefixes[: len(prefixes) - 1]
212 logger.debug("prefix_list=%s", prefixes)
213 logger.info("prefix_count=%s", prefix_count)
227 """Send a http POST request for creating a prefix list.
230 :param odl_ip: controller's ip address or hostname
232 :param port: controller's restconf port
234 :param uri: URI without /restconf/ to complete URL
236 :param auth: authentication tupple as (user, password)
238 :param prefix_base: IP address of the first prefix
240 :prefix_len: length of the prefix in bites (specifies the increment as well)
242 :param count: number of prefixes to be processed
244 :route_key: bool deciding route-key tag existence
246 :param xml_template: xml template for building the xml data (not used)
252 "Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
260 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
262 "POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204
277 """Send a http PUT request for updating the prefix list.
280 :param odl_ip: controller's ip address or hostname
282 :param port: controller's restconf port
284 :param uri: URI without /restconf/ to complete URL
286 :param auth: authentication tupple as (user, password)
288 :param prefix_base: IP address of the first prefix
290 :prefix_len: length of the prefix in bites (specifies the increment as well)
292 :param count: number of prefixes to be processed
294 :param xml_template: xml template for building the xml data (not used)
299 uri_add_prefix = uri + _uri_suffix_ipv4_routes
301 "Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
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)
324 """Send a consequent http POST request for adding prefixes.
327 :param odl_ip: controller's ip address or hostname
329 :param port: controller's restconf port
331 :param uri: URI without /restconf/ to complete URL
333 :param auth: authentication tupple as (user, password)
335 :param prefix_base: IP address of the first prefix
337 :prefix_len: length of the prefix in bites (specifies the increment as well)
339 :param count: number of prefixes to be processed
341 :param xml_template: xml template for building the xml data (not used)
347 "Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
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
360 "Adding prefix %s/%s to %s:%s/restconf/%s",
367 xml_stream = _stream_data(
368 xml_template, prefix, prefix_len, 1, route_key, element="ipv4-route"
377 expect_status_code=204,
382 odl_ip, port, uri, auth, prefix_base, prefix_len, count, xml_template=None
384 """Send a http DELETE requests for deleting prefixes.
387 :param odl_ip: controller's ip address or hostname
389 :param port: controller's restconf port
391 :param uri: URI without /restconf/ to complete URL
393 :param auth: authentication tupple as (user, password)
395 :param prefix_base: IP address of the first prefix
397 :prefix_len: length of the prefix in bites (specifies the increment as well)
399 :param count: number of prefixes to be processed
401 :param xml_template: xml template for building the xml data (not used)
407 "Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
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
421 "Deleting prefix %s/%s/%s from %s:%s/restconf/%s",
433 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len) + partkey,
438 def delete_all_prefixes(
448 """Send a http DELETE request for deleting all prefixes.
451 :param odl_ip: controller's ip address or hostname
453 :param port: controller's restconf port
455 :param uri: URI without /restconf/ to complete URL
457 :param auth: authentication tupple as (user, password)
459 :param prefix_base: IP address of the first prefix (not used)
461 :prefix_len: length of the prefix in bites (not used)
463 :param count: number of prefixes to be processed (not used)
465 :param xml_template: xml template for building the xml data (not used)
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)
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
481 if __name__ == "__main__":
482 parser = argparse.ArgumentParser(description="BGP application peer script")
485 type=ipaddr.IPv4Address,
487 help="ODL controller IP address",
489 parser.add_argument("--port", default="8181", help="ODL RESTCONF port")
494 help="Command to be performed." "post, put, add, delete, delete-all, get",
498 type=ipaddr.IPv4Address,
500 help="First prefix IP address",
503 "--prefixlen", type=int, help="Prefix length in bites", default=28
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")
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/",
517 help="File name of the xml data template",
518 default="ipv4-routes-template.xml",
523 action="store_const",
525 default=logging.INFO,
526 help="Set log level to error (default is info)",
531 action="store_const",
532 const=logging.WARNING,
533 default=logging.INFO,
534 help="Set log level to warning (default is info)",
539 action="store_const",
541 default=logging.INFO,
542 help="Set log level to info (default is info)",
547 action="store_const",
549 default=logging.INFO,
550 help="Set log level to debug (default is info)",
552 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
554 "--stream", default="", help="ODL Stream - oxygen, fluorine ..."
557 args = parser.parse_args()
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)
569 auth = (args.user, args.password)
573 command = args.command
574 prefix_base = args.prefix
575 prefix_len = args.prefixlen
579 xml_template = args.xml
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
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
590 if command == "post":
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)
633 total_test_execution_time = time.time() - test_start_time
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)