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, route_key=False, 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 :route_key: bool deciding route-key tag existence
56 :element: element to be returned
59 :yield xml_data: requested data by elements as xml data
61 global total_build_data_time_counter
63 routes = md.parse(xml_template)
65 routes_node = routes.getElementsByTagName("ipv4-routes")[0]
66 route_node = routes.getElementsByTagName("ipv4-route")[0]
67 routes_node.removeChild(route_node)
68 route_prefix = route_node.getElementsByTagName("prefix")[0]
70 key = route_node.getElementsByTagName("route-key")[0]
71 prefix_gap = 2 ** (32 - prefix_len)
72 prefix_index_list = range(count)
73 if element == routes_node.tagName:
74 lines = routes_node.toxml().splitlines()
75 xml_head = lines[0] + "\n"
76 xml_tail = "\n".join(lines[1:])
77 elif element == route_node.tagName:
80 route_node.setAttribute("xmlns", route_node.namespaceURI)
82 prefix_index_list = range(0)
84 for prefix_index in prefix_index_list:
85 build_data_timestamp = time.time()
86 prefix = prefix_base + prefix_index * prefix_gap
87 prefix_str = str(prefix) + "/" + str(prefix_len)
88 route_prefix.childNodes[0].nodeValue = prefix_str
90 key.childNodes[0].nodeValue = prefix_str
91 xml_data = route_node.toxml()
93 xml_data = xml_head + xml_data
94 if prefix_index == len(prefix_index_list) - 1:
95 xml_data = xml_data + xml_tail
96 chunk = prefix_index + 1
97 if not (chunk % 1000):
98 logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
100 logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
101 logger.debug("xml data\n%s", xml_data)
102 total_build_data_time_counter += time.time() - build_data_timestamp
106 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
107 """Send a http request.
110 :operation: GET, POST, PUT, DELETE
112 :param odl_ip: controller's ip address or hostname
114 :param port: controller's restconf port
116 :param uri: URI without /restconf/ to complete URL
118 :param auth: authentication credentials
120 :param xml_data: list of routes as xml data
123 :returns http response object
125 global total_response_time_counter
126 global total_number_of_responses_counter
128 ses = requests.Session()
130 url = _build_url(odl_ip, port, uri)
131 header = {"Content-Type": "application/xml"}
132 req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
135 send_request_timestamp = time.time()
136 rsp = ses.send(prep, timeout=60)
137 total_response_time_counter += time.time() - send_request_timestamp
138 total_number_of_responses_counter += 1
139 except requests.exceptions.Timeout:
140 logger.error("No response from %s", odl_ip)
142 if rsp.status_code == expect_status_code:
143 logger.debug("%s %s", rsp.request, rsp.request.url)
144 logger.debug("Request headers: %s:", rsp.request.headers)
145 logger.debug("Response: %s", rsp.text)
146 logger.debug("%s %s", rsp, rsp.reason)
148 logger.error("%s %s", rsp.request, rsp.request.url)
149 logger.error("Request headers: %s:", rsp.request.headers)
150 logger.error("Response: %s", rsp.text)
151 logger.error("%s %s", rsp, rsp.reason)
155 def get_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
156 count=None, xml_template=None):
157 """Send a http GET request for getting all prefixes.
160 :param odl_ip: controller's ip address or hostname
162 :param port: controller's restconf port
164 :param uri: URI without /restconf/ to complete URL
166 :param auth: authentication tupple as (user, password)
168 :param prefix_base: IP address of the first prefix
170 :prefix_len: length of the prefix in bites (specifies the increment as well)
172 :param count: number of prefixes to be processed
174 :param xml_template: xml template for building the xml data
180 logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
181 rsp = send_request("GET", odl_ip, port, uri, auth)
184 s = s.replace("{", "")
185 s = s.replace("}", "")
186 s = s.replace("[", "")
187 s = s.replace("]", "")
190 for item in s.split(","):
192 prefixes += item + ","
194 prefixes = prefixes[:len(prefixes) - 1]
195 logger.debug("prefix_list=%s", prefixes)
196 logger.info("prefix_count=%s", prefix_count)
199 def post_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
200 count=0, route_key=False, xml_template=None):
201 """Send a http POST request for creating a prefix list.
204 :param odl_ip: controller's ip address or hostname
206 :param port: controller's restconf port
208 :param uri: URI without /restconf/ to complete URL
210 :param auth: authentication tupple as (user, password)
212 :param prefix_base: IP address of the first prefix
214 :prefix_len: length of the prefix in bites (specifies the increment as well)
216 :param count: number of prefixes to be processed
218 :route_key: bool deciding route-key tag existence
220 :param xml_template: xml template for building the xml data (not used)
225 logger.info("Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
226 count, prefix_base, prefix_len, odl_ip, port, uri)
227 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
228 send_request("POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204)
231 def put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
232 route_key, xml_template=None):
233 """Send a http PUT request for updating the prefix list.
236 :param odl_ip: controller's ip address or hostname
238 :param port: controller's restconf port
240 :param uri: URI without /restconf/ to complete URL
242 :param auth: authentication tupple as (user, password)
244 :param prefix_base: IP address of the first prefix
246 :prefix_len: length of the prefix in bites (specifies the increment as well)
248 :param count: number of prefixes to be processed
250 :param xml_template: xml template for building the xml data (not used)
255 uri_add_prefix = uri + _uri_suffix_ipv4_routes
256 logger.info("Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
257 count, prefix_base, prefix_len, odl_ip, port, uri_add_prefix)
258 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
259 send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
262 def add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
263 route_key, xml_template=None):
264 """Send a consequent http POST request for adding prefixes.
267 :param odl_ip: controller's ip address or hostname
269 :param port: controller's restconf port
271 :param uri: URI without /restconf/ to complete URL
273 :param auth: authentication tupple as (user, password)
275 :param prefix_base: IP address of the first prefix
277 :prefix_len: length of the prefix in bites (specifies the increment as well)
279 :param count: number of prefixes to be processed
281 :param xml_template: xml template for building the xml data (not used)
286 logger.info("Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
287 count, prefix_base, prefix_len, odl_ip, port, uri)
288 uri_add_prefix = uri + _uri_suffix_ipv4_routes
289 prefix_gap = 2 ** (32 - prefix_len)
290 for prefix_index in range(count):
291 prefix = prefix_base + prefix_index * prefix_gap
292 logger.info("Adding prefix %s/%s to %s:%s/restconf/%s",
293 prefix, prefix_len, odl_ip, port, uri)
294 xml_stream = _stream_data(xml_template, prefix, prefix_len, 1, route_key,
295 element="ipv4-route")
296 send_request("POST", odl_ip, port, uri_add_prefix, auth,
297 xml_data=xml_stream, expect_status_code=204)
300 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
302 """Send a http DELETE requests for deleting prefixes.
305 :param odl_ip: controller's ip address or hostname
307 :param port: controller's restconf port
309 :param uri: URI without /restconf/ to complete URL
311 :param auth: authentication tupple as (user, password)
313 :param prefix_base: IP address of the first prefix
315 :prefix_len: length of the prefix in bites (specifies the increment as well)
317 :param count: number of prefixes to be processed
319 :param xml_template: xml template for building the xml data (not used)
324 logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
325 count, prefix_base, prefix_len, odl_ip, port, uri)
327 uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
328 prefix_gap = 2 ** (32 - prefix_len)
329 for prefix_index in range(count):
330 prefix = prefix_base + prefix_index * prefix_gap
331 logger.info("Deleting prefix %s/%s/%s from %s:%s/restconf/%s",
332 prefix, prefix_len, partkey, odl_ip, port, uri)
333 send_request("DELETE", odl_ip, port,
334 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len) + partkey, auth)
337 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
338 prefix_len=None, count=None, xml_template=None):
339 """Send a http DELETE request for deleting all prefixes.
342 :param odl_ip: controller's ip address or hostname
344 :param port: controller's restconf port
346 :param uri: URI without /restconf/ to complete URL
348 :param auth: authentication tupple as (user, password)
350 :param prefix_base: IP address of the first prefix (not used)
352 :prefix_len: length of the prefix in bites (not used)
354 :param count: number of prefixes to be processed (not used)
356 :param xml_template: xml template for building the xml data (not used)
361 logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
362 uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
363 send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
366 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
367 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
368 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/" # followed by IP address like 1.1.1.1%2F32
370 if __name__ == "__main__":
371 parser = argparse.ArgumentParser(description="BGP application peer script")
372 parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
373 help="ODL controller IP address")
374 parser.add_argument("--port", default="8181",
375 help="ODL RESTCONF port")
376 parser.add_argument("--command", choices=_commands, metavar="command",
377 help="Command to be performed."
378 "post, put, add, delete, delete-all, get")
379 parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
380 help="First prefix IP address")
381 parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
383 parser.add_argument("--count", type=int, help="Number of prefixes",
385 parser.add_argument("--user", help="Restconf user name", default="admin")
386 parser.add_argument("--password", help="Restconf password", default="admin")
387 parser.add_argument("--uri", help="The uri part of requests",
388 default="config/bgp-rib:application-rib/example-app-rib/"
389 "tables/bgp-types:ipv4-address-family/"
390 "bgp-types:unicast-subsequent-address-family/")
391 parser.add_argument("--xml", help="File name of the xml data template",
392 default="ipv4-routes-template.xml")
393 parser.add_argument("--error", dest="loglevel", action="store_const",
394 const=logging.ERROR, default=logging.INFO,
395 help="Set log level to error (default is info)")
396 parser.add_argument("--warning", dest="loglevel", action="store_const",
397 const=logging.WARNING, default=logging.INFO,
398 help="Set log level to warning (default is info)")
399 parser.add_argument("--info", dest="loglevel", action="store_const",
400 const=logging.INFO, default=logging.INFO,
401 help="Set log level to info (default is info)")
402 parser.add_argument("--debug", dest="loglevel", action="store_const",
403 const=logging.DEBUG, default=logging.INFO,
404 help="Set log level to debug (default is info)")
405 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
406 parser.add_argument("--stream", default="", help="Stream - oxygen, fluorine ...")
408 args = parser.parse_args()
410 logger = logging.getLogger("logger")
411 log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
412 console_handler = logging.StreamHandler()
413 file_handler = logging.FileHandler(args.logfile, mode="w")
414 console_handler.setFormatter(log_formatter)
415 file_handler.setFormatter(log_formatter)
416 logger.addHandler(console_handler)
417 logger.addHandler(file_handler)
418 logger.setLevel(args.loglevel)
420 auth = (args.user, args.password)
424 command = args.command
425 prefix_base = args.prefix
426 prefix_len = args.prefixlen
428 auth = (args.user, args.password)
430 # From Fluorine onward route-key argument is mandatory for identification.
431 route_key_stream = ["fluorine"]
432 [xml_template, route_key] = ["{}.{}".format(args.xml, args.stream), True] \
433 if args.stream in route_key_stream else [args.xml, False]
435 test_start_time = time.time()
436 total_build_data_time_counter = 0
437 total_response_time_counter = 0
438 total_number_of_responses_counter = 0
440 if command == "post":
441 post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
442 route_key, xml_template)
444 put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
445 route_key, xml_template)
447 add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
448 route_key, xml_template)
449 elif command == "delete":
450 delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
451 elif command == "delete-all":
452 delete_all_prefixes(odl_ip, port, uri, auth)
453 elif command == "get":
454 get_prefixes(odl_ip, port, uri, auth)
456 total_test_execution_time = time.time() - test_start_time
458 logger.info("Total test execution time: %.3fs", total_test_execution_time)
459 logger.info("Total build data time: %.3fs", total_build_data_time_counter)
460 logger.info("Total response time: %.3fs", total_response_time_counter)
461 logger.info("Total number of response(s): %s", total_number_of_responses_counter)