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
17 __author__ = "Radovan Sajben"
18 __copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
19 __license__ = "Eclipse Public License v1.0"
20 __email__ = "rsajben@cisco.com"
23 def _build_url(odl_ip, port, uri):
24 """Compose URL from generic IP, port and URI fragment.
27 :param odl_ip: controller's ip address or hostname
29 :param port: controller's restconf port
31 :param uri: URI without /restconf/ to complete URL
34 :returns url: full restconf url corresponding to params
37 url = "http://" + str(odl_ip) + ":" + port + "/restconf/" + uri
41 def _stream_data(xml_template, prefix_base, prefix_len, count, element="ipv4-routes"):
42 """Stream list of routes based on xml template. Memory non-consumable
43 data generation (on the fly).
46 :xml_template: xml template for routes
48 :prefix_base: first prefix IP address
50 :prefix_len: prefix length in bits
52 :count: number of routes to be generated
54 :element: element to be returned
57 :yield xml_data: requested data by elements as xml data
59 global total_build_data_time_counter
61 routes = md.parse(xml_template)
63 routes_node = routes.getElementsByTagName("ipv4-routes")[0]
64 route_node = routes.getElementsByTagName("ipv4-route")[0]
65 routes_node.removeChild(route_node)
66 route_prefix = route_node.getElementsByTagName("prefix")[0]
67 prefix_gap = 2 ** (32 - prefix_len)
68 prefix_index_list = range(count)
69 if element == routes_node.tagName:
70 lines = routes_node.toxml().splitlines()
71 xml_head = lines[0] + "\n"
72 xml_tail = "\n".join(lines[1:])
73 elif element == route_node.tagName:
76 route_node.setAttribute("xmlns", route_node.namespaceURI)
78 prefix_index_list = range(0)
80 for prefix_index in prefix_index_list:
81 build_data_timestamp = time.time()
82 prefix = prefix_base + prefix_index * prefix_gap
83 prefix_str = str(prefix) + "/" + str(prefix_len)
84 route_prefix.childNodes[0].nodeValue = prefix_str
85 xml_data = route_node.toxml()
87 xml_data = xml_head + xml_data
88 if prefix_index == len(prefix_index_list) - 1:
89 xml_data = xml_data + xml_tail
90 chunk = prefix_index + 1
91 if not (chunk % 1000):
92 logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
94 logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
95 logger.debug("xml data\n%s", xml_data)
96 total_build_data_time_counter += time.time() - build_data_timestamp
100 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
101 """Send a http request.
104 :operation: GET, POST, PUT, DELETE
106 :param odl_ip: controller's ip address or hostname
108 :param port: controller's restconf port
110 :param uri: URI without /restconf/ to complete URL
112 :param auth: authentication credentials
114 :param xml_data: list of routes as xml data
117 :returns http response object
119 global total_response_time_counter
120 global total_number_of_responses_counter
122 ses = requests.Session()
124 url = _build_url(odl_ip, port, uri)
125 header = {"Content-Type": "application/xml"}
126 req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
129 send_request_timestamp = time.time()
130 rsp = ses.send(prep, timeout=60)
131 total_response_time_counter += time.time() - send_request_timestamp
132 total_number_of_responses_counter += 1
133 except requests.exceptions.Timeout:
134 logger.error("No response from %s", odl_ip)
136 if rsp.status_code == expect_status_code:
137 logger.debug("%s %s", rsp.request, rsp.request.url)
138 logger.debug("Request headers: %s:", rsp.request.headers)
139 logger.debug("Response: %s", rsp.text)
140 logger.debug("%s %s", rsp, rsp.reason)
142 logger.error("%s %s", rsp.request, rsp.request.url)
143 logger.error("Request headers: %s:", rsp.request.headers)
144 logger.error("Response: %s", rsp.text)
145 logger.error("%s %s", rsp, rsp.reason)
149 def get_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
150 count=None, xml_template=None):
151 """Send a http GET request for getting all prefixes.
154 :param odl_ip: controller's ip address or hostname
156 :param port: controller's restconf port
158 :param uri: URI without /restconf/ to complete URL
160 :param auth: authentication tupple as (user, password)
162 :param prefix_base: IP address of the first prefix
164 :prefix_len: length of the prefix in bites (specifies the increment as well)
166 :param count: number of prefixes to be processed
168 :param xml_template: xml template for building the xml data
174 logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
175 rsp = send_request("GET", odl_ip, port, uri, auth)
178 s = s.replace("{", "")
179 s = s.replace("}", "")
180 s = s.replace("[", "")
181 s = s.replace("]", "")
184 for item in s.split(","):
186 prefixes += item + ","
188 prefixes = prefixes[:len(prefixes) - 1]
189 logger.debug("prefix_list=%s", prefixes)
190 logger.info("prefix_count=%s", prefix_count)
193 def post_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
194 count=0, xml_template=None):
195 """Send a http POST request for creating a prefix list.
198 :param odl_ip: controller's ip address or hostname
200 :param port: controller's restconf port
202 :param uri: URI without /restconf/ to complete URL
204 :param auth: authentication tupple as (user, password)
206 :param prefix_base: IP address of the first prefix
208 :prefix_len: length of the prefix in bites (specifies the increment as well)
210 :param count: number of prefixes to be processed
212 :param xml_template: xml template for building the xml data (not used)
217 logger.info("Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
218 count, prefix_base, prefix_len, odl_ip, port, uri)
219 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
220 send_request("POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204)
223 def put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
225 """Send a http PUT request for updating the prefix list.
228 :param odl_ip: controller's ip address or hostname
230 :param port: controller's restconf port
232 :param uri: URI without /restconf/ to complete URL
234 :param auth: authentication tupple as (user, password)
236 :param prefix_base: IP address of the first prefix
238 :prefix_len: length of the prefix in bites (specifies the increment as well)
240 :param count: number of prefixes to be processed
242 :param xml_template: xml template for building the xml data (not used)
247 uri_add_prefix = uri + _uri_suffix_ipv4_routes
248 logger.info("Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
249 count, prefix_base, prefix_len, odl_ip, port, uri_add_prefix)
250 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
251 send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
254 def add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
256 """Send a consequent http POST request for adding prefixes.
259 :param odl_ip: controller's ip address or hostname
261 :param port: controller's restconf port
263 :param uri: URI without /restconf/ to complete URL
265 :param auth: authentication tupple as (user, password)
267 :param prefix_base: IP address of the first prefix
269 :prefix_len: length of the prefix in bites (specifies the increment as well)
271 :param count: number of prefixes to be processed
273 :param xml_template: xml template for building the xml data (not used)
278 logger.info("Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
279 count, prefix_base, prefix_len, odl_ip, port, uri)
280 uri_add_prefix = uri + _uri_suffix_ipv4_routes
281 prefix_gap = 2 ** (32 - prefix_len)
282 for prefix_index in range(count):
283 prefix = prefix_base + prefix_index * prefix_gap
284 logger.info("Adding prefix %s/%s to %s:%s/restconf/%s",
285 prefix, prefix_len, odl_ip, port, uri)
286 xml_stream = _stream_data(xml_template, prefix, prefix_len, 1, "ipv4-route")
287 send_request("POST", odl_ip, port, uri_add_prefix, auth,
288 xml_data=xml_stream, expect_status_code=204)
291 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
293 """Send a http DELETE requests for deleting prefixes.
296 :param odl_ip: controller's ip address or hostname
298 :param port: controller's restconf port
300 :param uri: URI without /restconf/ to complete URL
302 :param auth: authentication tupple as (user, password)
304 :param prefix_base: IP address of the first prefix
306 :prefix_len: length of the prefix in bites (specifies the increment as well)
308 :param count: number of prefixes to be processed
310 :param xml_template: xml template for building the xml data (not used)
315 logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
316 count, prefix_base, prefix_len, odl_ip, port, uri)
317 uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
318 prefix_gap = 2 ** (32 - prefix_len)
319 for prefix_index in range(count):
320 prefix = prefix_base + prefix_index * prefix_gap
321 logger.info("Deleting prefix %s/%s from %s:%s/restconf/%s",
322 prefix, prefix_len, odl_ip, port, uri)
323 send_request("DELETE", odl_ip, port,
324 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len), auth)
327 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
328 prefix_len=None, count=None, xml_template=None):
329 """Send a http DELETE request for deleting all prefixes.
332 :param odl_ip: controller's ip address or hostname
334 :param port: controller's restconf port
336 :param uri: URI without /restconf/ to complete URL
338 :param auth: authentication tupple as (user, password)
340 :param prefix_base: IP address of the first prefix (not used)
342 :prefix_len: length of the prefix in bites (not used)
344 :param count: number of prefixes to be processed (not used)
346 :param xml_template: xml template for building the xml data (not used)
351 logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
352 uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
353 send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
356 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
357 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
358 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/" # followed by IP address like 1.1.1.1%2F32
360 if __name__ == "__main__":
361 parser = argparse.ArgumentParser(description="BGP application peer script")
362 parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
363 help="ODL controller IP address")
364 parser.add_argument("--port", default="8181",
365 help="ODL RESTCONF port")
366 parser.add_argument("--command", choices=_commands, metavar="command",
367 help="Command to be performed."
368 "post, put, add, delete, delete-all, get")
369 parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
370 help="First prefix IP address")
371 parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
373 parser.add_argument("--count", type=int, help="Number of prefixes",
375 parser.add_argument("--user", help="Restconf user name", default="admin")
376 parser.add_argument("--password", help="Restconf password", default="admin")
377 parser.add_argument("--uri", help="The uri part of requests",
378 default="config/bgp-rib:application-rib/example-app-rib/"
379 "tables/bgp-types:ipv4-address-family/"
380 "bgp-types:unicast-subsequent-address-family/")
381 parser.add_argument("--xml", help="File name of the xml data template",
382 default="ipv4-routes-template.xml")
383 parser.add_argument("--error", dest="loglevel", action="store_const",
384 const=logging.ERROR, default=logging.INFO,
385 help="Set log level to error (default is info)")
386 parser.add_argument("--warning", dest="loglevel", action="store_const",
387 const=logging.WARNING, default=logging.INFO,
388 help="Set log level to warning (default is info)")
389 parser.add_argument("--info", dest="loglevel", action="store_const",
390 const=logging.INFO, default=logging.INFO,
391 help="Set log level to info (default is info)")
392 parser.add_argument("--debug", dest="loglevel", action="store_const",
393 const=logging.DEBUG, default=logging.INFO,
394 help="Set log level to debug (default is info)")
395 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
397 args = parser.parse_args()
399 logger = logging.getLogger("logger")
400 log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
401 console_handler = logging.StreamHandler()
402 file_handler = logging.FileHandler(args.logfile, mode="w")
403 console_handler.setFormatter(log_formatter)
404 file_handler.setFormatter(log_formatter)
405 logger.addHandler(console_handler)
406 logger.addHandler(file_handler)
407 logger.setLevel(args.loglevel)
409 auth = (args.user, args.password)
413 command = args.command
414 prefix_base = args.prefix
415 prefix_len = args.prefixlen
417 auth = (args.user, args.password)
419 xml_template = args.xml
421 test_start_time = time.time()
422 total_build_data_time_counter = 0
423 total_response_time_counter = 0
424 total_number_of_responses_counter = 0
426 if command == "post":
427 post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
430 put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
433 add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
435 elif command == "delete":
436 delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
437 elif command == "delete-all":
438 delete_all_prefixes(odl_ip, port, uri, auth)
439 elif command == "get":
440 get_prefixes(odl_ip, port, uri, auth)
442 total_test_execution_time = time.time() - test_start_time
444 logger.info("Total test execution time: %.3fs", total_test_execution_time)
445 logger.info("Total build data time: %.3fs", total_build_data_time_counter)
446 logger.info("Total response time: %.3fs", total_response_time_counter)
447 logger.info("Total number of response(s): %s", total_number_of_responses_counter)