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 send_request("POST", odl_ip, port, uri_add_prefix, auth,
296 xml_data=xml_stream, expect_status_code=204)
299 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
301 """Send a http DELETE requests for deleting prefixes.
304 :param odl_ip: controller's ip address or hostname
306 :param port: controller's restconf port
308 :param uri: URI without /restconf/ to complete URL
310 :param auth: authentication tupple as (user, password)
312 :param prefix_base: IP address of the first prefix
314 :prefix_len: length of the prefix in bites (specifies the increment as well)
316 :param count: number of prefixes to be processed
318 :param xml_template: xml template for building the xml data (not used)
323 logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
324 count, prefix_base, prefix_len, odl_ip, port, uri)
326 uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
327 prefix_gap = 2 ** (32 - prefix_len)
328 for prefix_index in range(count):
329 prefix = prefix_base + prefix_index * prefix_gap
330 logger.info("Deleting prefix %s/%s/%s from %s:%s/restconf/%s",
331 prefix, prefix_len, partkey, odl_ip, port, uri)
332 send_request("DELETE", odl_ip, port,
333 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len) + partkey, auth)
336 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
337 prefix_len=None, count=None, xml_template=None):
338 """Send a http DELETE request for deleting all prefixes.
341 :param odl_ip: controller's ip address or hostname
343 :param port: controller's restconf port
345 :param uri: URI without /restconf/ to complete URL
347 :param auth: authentication tupple as (user, password)
349 :param prefix_base: IP address of the first prefix (not used)
351 :prefix_len: length of the prefix in bites (not used)
353 :param count: number of prefixes to be processed (not used)
355 :param xml_template: xml template for building the xml data (not used)
360 logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
361 uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
362 send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
365 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
366 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
367 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/" # followed by IP address like 1.1.1.1%2F32
369 if __name__ == "__main__":
370 parser = argparse.ArgumentParser(description="BGP application peer script")
371 parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
372 help="ODL controller IP address")
373 parser.add_argument("--port", default="8181",
374 help="ODL RESTCONF port")
375 parser.add_argument("--command", choices=_commands, metavar="command",
376 help="Command to be performed."
377 "post, put, add, delete, delete-all, get")
378 parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
379 help="First prefix IP address")
380 parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
382 parser.add_argument("--count", type=int, help="Number of prefixes",
384 parser.add_argument("--user", help="Restconf user name", default="admin")
385 parser.add_argument("--password", help="Restconf password", default="admin")
386 parser.add_argument("--uri", help="The uri part of requests",
387 default="config/bgp-rib:application-rib/example-app-rib/"
388 "tables/bgp-types:ipv4-address-family/"
389 "bgp-types:unicast-subsequent-address-family/")
390 parser.add_argument("--xml", help="File name of the xml data template",
391 default="ipv4-routes-template.xml")
392 parser.add_argument("--error", dest="loglevel", action="store_const",
393 const=logging.ERROR, default=logging.INFO,
394 help="Set log level to error (default is info)")
395 parser.add_argument("--warning", dest="loglevel", action="store_const",
396 const=logging.WARNING, default=logging.INFO,
397 help="Set log level to warning (default is info)")
398 parser.add_argument("--info", dest="loglevel", action="store_const",
399 const=logging.INFO, default=logging.INFO,
400 help="Set log level to info (default is info)")
401 parser.add_argument("--debug", dest="loglevel", action="store_const",
402 const=logging.DEBUG, default=logging.INFO,
403 help="Set log level to debug (default is info)")
404 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
405 parser.add_argument("--stream", default="", help="Stream - oxygen, fluorine ...")
407 args = parser.parse_args()
409 logger = logging.getLogger("logger")
410 log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
411 console_handler = logging.StreamHandler()
412 file_handler = logging.FileHandler(args.logfile, mode="w")
413 console_handler.setFormatter(log_formatter)
414 file_handler.setFormatter(log_formatter)
415 logger.addHandler(console_handler)
416 logger.addHandler(file_handler)
417 logger.setLevel(args.loglevel)
419 auth = (args.user, args.password)
423 command = args.command
424 prefix_base = args.prefix
425 prefix_len = args.prefixlen
427 auth = (args.user, args.password)
429 # From Fluorine onward route-key argument is mandatory for identification.
430 route_key_stream = ["fluorine"]
431 [xml_template, route_key] = ["{}.{}".format(args.xml, args.stream), True] \
432 if args.stream in route_key_stream else [args.xml, False]
434 test_start_time = time.time()
435 total_build_data_time_counter = 0
436 total_response_time_counter = 0
437 total_number_of_responses_counter = 0
439 if command == "post":
440 post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
441 route_key, xml_template)
443 put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
444 route_key, xml_template)
446 add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
447 route_key, xml_template)
448 elif command == "delete":
449 delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
450 elif command == "delete-all":
451 delete_all_prefixes(odl_ip, port, uri, auth)
452 elif command == "get":
453 get_prefixes(odl_ip, port, uri, auth)
455 total_test_execution_time = time.time() - test_start_time
457 logger.info("Total test execution time: %.3fs", total_test_execution_time)
458 logger.info("Total build data time: %.3fs", total_build_data_time_counter)
459 logger.info("Total response time: %.3fs", total_response_time_counter)
460 logger.info("Total number of response(s): %s", total_number_of_responses_counter)