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
42 def _stream_data(xml_template, prefix_base, prefix_len, count, route_key=False, element="ipv4-routes"):
43 """Stream list of routes based on xml template. Memory non-consumable
44 data generation (on the fly).
47 :xml_template: xml template for routes
49 :prefix_base: first prefix IP address
51 :prefix_len: prefix length in bits
53 :count: number of routes to be generated
55 :route_key: bool deciding route-key tag existence
57 :element: element to be returned
60 :yield xml_data: requested data by elements as xml data
62 global total_build_data_time_counter
63 if os.path.isfile(xml_template + "." + stream):
64 routes = md.parse(xml_template + "." + stream)
65 elif os.path.isfile(xml_template):
66 routes = md.parse(xml_template)
68 logger.error("Template '{}' does not exist.".format(xml_template))
70 routes_node = routes.getElementsByTagName("ipv4-routes")[0]
71 route_node = routes.getElementsByTagName("ipv4-route")[0]
72 routes_node.removeChild(route_node)
73 route_prefix = route_node.getElementsByTagName("prefix")[0]
75 key = route_node.getElementsByTagName("route-key")[0]
76 prefix_gap = 2 ** (32 - prefix_len)
77 prefix_index_list = range(count)
78 if element == routes_node.tagName:
79 lines = routes_node.toxml().splitlines()
80 xml_head = lines[0] + "\n"
81 xml_tail = "\n".join(lines[1:])
82 elif element == route_node.tagName:
85 route_node.setAttribute("xmlns", route_node.namespaceURI)
87 prefix_index_list = range(0)
89 for prefix_index in prefix_index_list:
90 build_data_timestamp = time.time()
91 prefix = prefix_base + prefix_index * prefix_gap
92 prefix_str = str(prefix) + "/" + str(prefix_len)
93 route_prefix.childNodes[0].nodeValue = prefix_str
95 key.childNodes[0].nodeValue = prefix_str
96 xml_data = route_node.toxml()
98 xml_data = xml_head + xml_data
99 if prefix_index == len(prefix_index_list) - 1:
100 xml_data = xml_data + xml_tail
101 chunk = prefix_index + 1
102 if not (chunk % 1000):
103 logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
105 logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
106 logger.debug("xml data\n%s", xml_data)
107 total_build_data_time_counter += time.time() - build_data_timestamp
111 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
112 """Send a http request.
115 :operation: GET, POST, PUT, DELETE
117 :param odl_ip: controller's ip address or hostname
119 :param port: controller's restconf port
121 :param uri: URI without /restconf/ to complete URL
123 :param auth: authentication credentials
125 :param xml_data: list of routes as xml data
128 :returns http response object
130 global total_response_time_counter
131 global total_number_of_responses_counter
133 ses = requests.Session()
135 url = _build_url(odl_ip, port, uri)
136 header = {"Content-Type": "application/xml"}
137 req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
140 send_request_timestamp = time.time()
141 rsp = ses.send(prep, timeout=60)
142 total_response_time_counter += time.time() - send_request_timestamp
143 total_number_of_responses_counter += 1
144 except requests.exceptions.Timeout:
145 logger.error("No response from %s", odl_ip)
147 if rsp.status_code == expect_status_code:
148 logger.debug("%s %s", rsp.request, rsp.request.url)
149 logger.debug("Request headers: %s:", rsp.request.headers)
150 logger.debug("Response: %s", rsp.text)
151 logger.debug("%s %s", rsp, rsp.reason)
153 logger.error("%s %s", rsp.request, rsp.request.url)
154 logger.error("Request headers: %s:", rsp.request.headers)
155 logger.error("Response: %s", rsp.text)
156 logger.error("%s %s", rsp, rsp.reason)
160 def get_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
161 count=None, xml_template=None):
162 """Send a http GET request for getting all prefixes.
165 :param odl_ip: controller's ip address or hostname
167 :param port: controller's restconf port
169 :param uri: URI without /restconf/ to complete URL
171 :param auth: authentication tupple as (user, password)
173 :param prefix_base: IP address of the first prefix
175 :prefix_len: length of the prefix in bites (specifies the increment as well)
177 :param count: number of prefixes to be processed
179 :param xml_template: xml template for building the xml data
185 logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
186 rsp = send_request("GET", odl_ip, port, uri, auth)
189 s = s.replace("{", "")
190 s = s.replace("}", "")
191 s = s.replace("[", "")
192 s = s.replace("]", "")
195 for item in s.split(","):
197 prefixes += item + ","
199 prefixes = prefixes[:len(prefixes) - 1]
200 logger.debug("prefix_list=%s", prefixes)
201 logger.info("prefix_count=%s", prefix_count)
204 def post_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
205 count=0, route_key=False, xml_template=None):
206 """Send a http POST request for creating a prefix list.
209 :param odl_ip: controller's ip address or hostname
211 :param port: controller's restconf port
213 :param uri: URI without /restconf/ to complete URL
215 :param auth: authentication tupple as (user, password)
217 :param prefix_base: IP address of the first prefix
219 :prefix_len: length of the prefix in bites (specifies the increment as well)
221 :param count: number of prefixes to be processed
223 :route_key: bool deciding route-key tag existence
225 :param xml_template: xml template for building the xml data (not used)
230 logger.info("Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
231 count, prefix_base, prefix_len, odl_ip, port, uri)
232 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
233 send_request("POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204)
236 def put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
237 route_key, xml_template=None):
238 """Send a http PUT request for updating the prefix list.
241 :param odl_ip: controller's ip address or hostname
243 :param port: controller's restconf port
245 :param uri: URI without /restconf/ to complete URL
247 :param auth: authentication tupple as (user, password)
249 :param prefix_base: IP address of the first prefix
251 :prefix_len: length of the prefix in bites (specifies the increment as well)
253 :param count: number of prefixes to be processed
255 :param xml_template: xml template for building the xml data (not used)
260 uri_add_prefix = uri + _uri_suffix_ipv4_routes
261 logger.info("Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
262 count, prefix_base, prefix_len, odl_ip, port, uri_add_prefix)
263 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count, route_key)
264 send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
267 def add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
268 route_key, xml_template=None):
269 """Send a consequent http POST request for adding prefixes.
272 :param odl_ip: controller's ip address or hostname
274 :param port: controller's restconf port
276 :param uri: URI without /restconf/ to complete URL
278 :param auth: authentication tupple as (user, password)
280 :param prefix_base: IP address of the first prefix
282 :prefix_len: length of the prefix in bites (specifies the increment as well)
284 :param count: number of prefixes to be processed
286 :param xml_template: xml template for building the xml data (not used)
291 logger.info("Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
292 count, prefix_base, prefix_len, odl_ip, port, uri)
293 uri_add_prefix = uri + _uri_suffix_ipv4_routes
294 prefix_gap = 2 ** (32 - prefix_len)
295 for prefix_index in range(count):
296 prefix = prefix_base + prefix_index * prefix_gap
297 logger.info("Adding prefix %s/%s to %s:%s/restconf/%s",
298 prefix, prefix_len, odl_ip, port, uri)
299 xml_stream = _stream_data(xml_template, prefix, prefix_len, 1, route_key,
300 element="ipv4-route")
301 send_request("POST", odl_ip, port, uri_add_prefix, auth,
302 xml_data=xml_stream, expect_status_code=204)
305 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
307 """Send a http DELETE requests for deleting prefixes.
310 :param odl_ip: controller's ip address or hostname
312 :param port: controller's restconf port
314 :param uri: URI without /restconf/ to complete URL
316 :param auth: authentication tupple as (user, password)
318 :param prefix_base: IP address of the first prefix
320 :prefix_len: length of the prefix in bites (specifies the increment as well)
322 :param count: number of prefixes to be processed
324 :param xml_template: xml template for building the xml data (not used)
329 logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
330 count, prefix_base, prefix_len, odl_ip, port, uri)
332 uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
333 prefix_gap = 2 ** (32 - prefix_len)
334 for prefix_index in range(count):
335 prefix = prefix_base + prefix_index * prefix_gap
336 logger.info("Deleting prefix %s/%s/%s from %s:%s/restconf/%s",
337 prefix, prefix_len, partkey, odl_ip, port, uri)
338 send_request("DELETE", odl_ip, port,
339 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len) + partkey, auth)
342 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
343 prefix_len=None, count=None, xml_template=None):
344 """Send a http DELETE request for deleting all prefixes.
347 :param odl_ip: controller's ip address or hostname
349 :param port: controller's restconf port
351 :param uri: URI without /restconf/ to complete URL
353 :param auth: authentication tupple as (user, password)
355 :param prefix_base: IP address of the first prefix (not used)
357 :prefix_len: length of the prefix in bites (not used)
359 :param count: number of prefixes to be processed (not used)
361 :param xml_template: xml template for building the xml data (not used)
366 logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
367 uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
368 send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
371 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
372 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
373 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/" # followed by IP address like 1.1.1.1%2F32
375 if __name__ == "__main__":
376 parser = argparse.ArgumentParser(description="BGP application peer script")
377 parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
378 help="ODL controller IP address")
379 parser.add_argument("--port", default="8181",
380 help="ODL RESTCONF port")
381 parser.add_argument("--command", choices=_commands, metavar="command",
382 help="Command to be performed."
383 "post, put, add, delete, delete-all, get")
384 parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
385 help="First prefix IP address")
386 parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
388 parser.add_argument("--count", type=int, help="Number of prefixes",
390 parser.add_argument("--user", help="Restconf user name", default="admin")
391 parser.add_argument("--password", help="Restconf password", default="admin")
392 parser.add_argument("--uri", help="The uri part of requests",
393 default="config/bgp-rib:application-rib/example-app-rib/"
394 "tables/bgp-types:ipv4-address-family/"
395 "bgp-types:unicast-subsequent-address-family/")
396 parser.add_argument("--xml", help="File name of the xml data template",
397 default="ipv4-routes-template.xml")
398 parser.add_argument("--error", dest="loglevel", action="store_const",
399 const=logging.ERROR, default=logging.INFO,
400 help="Set log level to error (default is info)")
401 parser.add_argument("--warning", dest="loglevel", action="store_const",
402 const=logging.WARNING, default=logging.INFO,
403 help="Set log level to warning (default is info)")
404 parser.add_argument("--info", dest="loglevel", action="store_const",
405 const=logging.INFO, default=logging.INFO,
406 help="Set log level to info (default is info)")
407 parser.add_argument("--debug", dest="loglevel", action="store_const",
408 const=logging.DEBUG, default=logging.INFO,
409 help="Set log level to debug (default is info)")
410 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
411 parser.add_argument("--stream", default="", help="ODL Stream - oxygen, fluorine ...")
413 args = parser.parse_args()
415 logger = logging.getLogger("logger")
416 log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
417 console_handler = logging.StreamHandler()
418 file_handler = logging.FileHandler(args.logfile, mode="w")
419 console_handler.setFormatter(log_formatter)
420 file_handler.setFormatter(log_formatter)
421 logger.addHandler(console_handler)
422 logger.addHandler(file_handler)
423 logger.setLevel(args.loglevel)
425 auth = (args.user, args.password)
429 command = args.command
430 prefix_base = args.prefix
431 prefix_len = args.prefixlen
435 xml_template = args.xml
437 # From Fluorine onward route-key argument is mandatory for identification.
438 route_key_stream = ["oxygen"]
439 route_key = True if args.stream not in route_key_stream else False
441 test_start_time = time.time()
442 total_build_data_time_counter = 0
443 total_response_time_counter = 0
444 total_number_of_responses_counter = 0
446 if command == "post":
447 post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
448 route_key, xml_template)
450 put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
451 route_key, xml_template)
453 add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
454 route_key, xml_template)
455 elif command == "delete":
456 delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
457 elif command == "delete-all":
458 delete_all_prefixes(odl_ip, port, uri, auth)
459 elif command == "get":
460 get_prefixes(odl_ip, port, uri, auth)
462 total_test_execution_time = time.time() - test_start_time
464 logger.info("Total test execution time: %.3fs", total_test_execution_time)
465 logger.info("Total build data time: %.3fs", total_build_data_time_counter)
466 logger.info("Total response time: %.3fs", total_response_time_counter)
467 logger.info("Total number of response(s): %s", total_number_of_responses_counter)