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
9 __author__ = "Radovan Sajben"
10 __copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
11 __license__ = "Eclipse Public License v1.0"
12 __email__ = "rsajben@cisco.com"
19 import xml.dom.minidom as md
22 def _build_url(odl_ip, port, uri):
23 """Compose URL from generic IP, port and URI fragment.
26 :param odl_ip: controller's ip address or hostname
28 :param port: controller's restconf port
30 :param uri: URI without /restconf/ to complete URL
33 :returns url: full restconf url corresponding to params
36 url = "http://" + str(odl_ip) + ":" + port + "/restconf/" + uri
40 def _stream_data(xml_template, prefix_base, prefix_len, count, element="ipv4-routes"):
41 """Stream list of routes based on xml template. Memory non-consumable
42 data generation (on the fly).
45 :xml_template: xml template for routes
47 :prefix_base: first prefix IP address
49 :prefix_len: prefix length in bits
51 :count: number of routes to be generated
53 :element: element to be returned
56 :yield xml_data: requested data by elements as xml data
58 global total_build_data_time_counter
60 routes = md.parse(xml_template)
62 routes_node = routes.getElementsByTagName("ipv4-routes")[0]
63 route_node = routes.getElementsByTagName("ipv4-route")[0]
64 routes_node.removeChild(route_node)
65 route_prefix = route_node.getElementsByTagName("prefix")[0]
66 prefix_gap = 2 ** (32 - prefix_len)
67 prefix_index_list = range(count)
68 if element == routes_node.tagName:
69 lines = routes_node.toxml().splitlines()
70 xml_head = lines[0] + "\n"
71 xml_tail = "\n".join(lines[1:])
72 elif element == route_node.tagName:
75 route_node.setAttribute("xmlns", route_node.namespaceURI)
77 prefix_index_list = range(0)
79 for prefix_index in prefix_index_list:
80 build_data_timestamp = time.time()
81 prefix = prefix_base + prefix_index * prefix_gap
82 prefix_str = str(prefix) + "/" + str(prefix_len)
83 route_prefix.childNodes[0].nodeValue = prefix_str
84 xml_data = route_node.toxml()
86 xml_data = xml_head + xml_data
87 if prefix_index == len(prefix_index_list) - 1:
88 xml_data = xml_data + xml_tail
89 chunk = prefix_index + 1
90 if not (chunk % 1000):
91 logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
93 logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
94 logger.debug("xml data\n%s", xml_data)
95 total_build_data_time_counter += time.time() - build_data_timestamp
99 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
100 """Send a http request.
103 :operation: GET, POST, PUT, DELETE
105 :param odl_ip: controller's ip address or hostname
107 :param port: controller's restconf port
109 :param uri: URI without /restconf/ to complete URL
111 :param auth: authentication credentials
113 :param xml_data: list of routes as xml data
116 :returns http response object
118 global total_response_time_counter
119 global total_number_of_responses_counter
121 ses = requests.Session()
123 url = _build_url(odl_ip, port, uri)
124 header = {"Content-Type": "application/xml"}
125 req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
128 send_request_timestamp = time.time()
129 rsp = ses.send(prep, timeout=60)
130 total_response_time_counter += time.time() - send_request_timestamp
131 total_number_of_responses_counter += 1
132 except requests.exceptions.Timeout:
133 logger.error("No response from %s", odl_ip)
135 if rsp.status_code == expect_status_code:
136 logger.debug("%s %s", rsp.request, rsp.request.url)
137 logger.debug("Request headers: %s:", rsp.request.headers)
138 logger.debug("Response: %s", rsp.text)
139 logger.debug("%s %s", rsp, rsp.reason)
141 logger.error("%s %s", rsp.request, rsp.request.url)
142 logger.error("Request headers: %s:", rsp.request.headers)
143 logger.error("Response: %s", rsp.text)
144 logger.error("%s %s", rsp, rsp.reason)
148 def get_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
149 count=None, xml_template=None):
150 """Send a http GET request for getting all prefixes.
153 :param odl_ip: controller's ip address or hostname
155 :param port: controller's restconf port
157 :param uri: URI without /restconf/ to complete URL
159 :param auth: authentication tupple as (user, password)
161 :param prefix_base: IP address of the first prefix
163 :prefix_len: length of the prefix in bites (specifies the increment as well)
165 :param count: number of prefixes to be processed
167 :param xml_template: xml template for building the xml data
173 logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
174 rsp = send_request("GET", odl_ip, port, uri, auth)
177 s = s.replace("{", "")
178 s = s.replace("}", "")
179 s = s.replace("[", "")
180 s = s.replace("]", "")
183 for item in s.split(","):
185 prefixes += item + ","
187 prefixes = prefixes[:len(prefixes)-1]
188 logger.debug("prefix_list=%s", prefixes)
189 logger.info("prefix_count=%s", prefix_count)
192 def post_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
193 count=0, xml_template=None):
194 """Send a http POST request for creating a prefix list.
197 :param odl_ip: controller's ip address or hostname
199 :param port: controller's restconf port
201 :param uri: URI without /restconf/ to complete URL
203 :param auth: authentication tupple as (user, password)
205 :param prefix_base: IP address of the first prefix
207 :prefix_len: length of the prefix in bites (specifies the increment as well)
209 :param count: number of prefixes to be processed
211 :param xml_template: xml template for building the xml data (not used)
216 logger.info("Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
217 count, prefix_base, prefix_len, odl_ip, port, uri)
218 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
219 send_request("POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204)
222 def put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
224 """Send a http PUT request for updating the prefix list.
227 :param odl_ip: controller's ip address or hostname
229 :param port: controller's restconf port
231 :param uri: URI without /restconf/ to complete URL
233 :param auth: authentication tupple as (user, password)
235 :param prefix_base: IP address of the first prefix
237 :prefix_len: length of the prefix in bites (specifies the increment as well)
239 :param count: number of prefixes to be processed
241 :param xml_template: xml template for building the xml data (not used)
246 uri_add_prefix = uri + _uri_suffix_ipv4_routes
247 logger.info("Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
248 count, prefix_base, prefix_len, odl_ip, port, uri_add_prefix)
249 xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
250 send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
253 def add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
255 """Send a consequent http POST request for adding prefixes.
258 :param odl_ip: controller's ip address or hostname
260 :param port: controller's restconf port
262 :param uri: URI without /restconf/ to complete URL
264 :param auth: authentication tupple as (user, password)
266 :param prefix_base: IP address of the first prefix
268 :prefix_len: length of the prefix in bites (specifies the increment as well)
270 :param count: number of prefixes to be processed
272 :param xml_template: xml template for building the xml data (not used)
277 logger.info("Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
278 count, prefix_base, prefix_len, odl_ip, port, uri)
279 uri_add_prefix = uri + _uri_suffix_ipv4_routes
280 prefix_gap = 2 ** (32 - prefix_len)
281 for prefix_index in range(count):
282 prefix = prefix_base + prefix_index * prefix_gap
283 logger.info("Adding prefix %s/%s to %s:%s/restconf/%s",
284 prefix, prefix_len, odl_ip, port, uri)
285 xml_stream = _stream_data(xml_template, prefix, prefix_len, 1, "ipv4-route")
286 send_request("POST", odl_ip, port, uri_add_prefix, auth,
287 xml_data=xml_stream, expect_status_code=204)
290 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
292 """Send a http DELETE requests for deleting prefixes.
295 :param odl_ip: controller's ip address or hostname
297 :param port: controller's restconf port
299 :param uri: URI without /restconf/ to complete URL
301 :param auth: authentication tupple as (user, password)
303 :param prefix_base: IP address of the first prefix
305 :prefix_len: length of the prefix in bites (specifies the increment as well)
307 :param count: number of prefixes to be processed
309 :param xml_template: xml template for building the xml data (not used)
314 logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
315 count, prefix_base, prefix_len, odl_ip, port, uri)
316 uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
317 prefix_gap = 2 ** (32 - prefix_len)
318 for prefix_index in range(count):
319 prefix = prefix_base + prefix_index * prefix_gap
320 logger.info("Deleting prefix %s/%s from %s:%s/restconf/%s",
321 prefix, prefix_len, odl_ip, port, uri)
322 send_request("DELETE", odl_ip, port,
323 uri_del_prefix + str(prefix) + "%2F" + str(prefix_len), auth)
326 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
327 prefix_len=None, count=None, xml_template=None):
328 """Send a http DELETE request for deleting all prefixes.
331 :param odl_ip: controller's ip address or hostname
333 :param port: controller's restconf port
335 :param uri: URI without /restconf/ to complete URL
337 :param auth: authentication tupple as (user, password)
339 :param prefix_base: IP address of the first prefix (not used)
341 :prefix_len: length of the prefix in bites (not used)
343 :param count: number of prefixes to be processed (not used)
345 :param xml_template: xml template for building the xml data (not used)
350 logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
351 uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
352 send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
355 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
356 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
357 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/" # followed by IP address like 1.1.1.1%2F32
359 if __name__ == "__main__":
360 parser = argparse.ArgumentParser(description="BGP application peer script")
361 parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
362 help="ODL controller IP address")
363 parser.add_argument("--port", default="8181",
364 help="ODL RESTCONF port")
365 parser.add_argument("--command", choices=_commands, metavar="command",
366 help="Command to be performed."
367 "post, put, add, delete, delete-all, get")
368 parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
369 help="First prefix IP address")
370 parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
372 parser.add_argument("--count", type=int, help="Number of prefixes",
374 parser.add_argument("--user", help="Restconf user name", default="admin")
375 parser.add_argument("--password", help="Restconf password", default="admin")
376 parser.add_argument("--uri", help="The uri part of requests",
377 default="config/bgp-rib:application-rib/example-app-rib/"
378 "tables/bgp-types:ipv4-address-family/"
379 "bgp-types:unicast-subsequent-address-family/")
380 parser.add_argument("--xml", help="File name of the xml data template",
381 default="ipv4-routes-template.xml")
382 parser.add_argument("--error", dest="loglevel", action="store_const",
383 const=logging.ERROR, default=logging.INFO,
384 help="Set log level to error (default is info)")
385 parser.add_argument("--warning", dest="loglevel", action="store_const",
386 const=logging.WARNING, default=logging.INFO,
387 help="Set log level to warning (default is info)")
388 parser.add_argument("--info", dest="loglevel", action="store_const",
389 const=logging.INFO, default=logging.INFO,
390 help="Set log level to info (default is info)")
391 parser.add_argument("--debug", dest="loglevel", action="store_const",
392 const=logging.DEBUG, default=logging.INFO,
393 help="Set log level to debug (default is info)")
394 parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
396 args = parser.parse_args()
398 logger = logging.getLogger("logger")
399 log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
400 console_handler = logging.StreamHandler()
401 file_handler = logging.FileHandler(args.logfile, mode="w")
402 console_handler.setFormatter(log_formatter)
403 file_handler.setFormatter(log_formatter)
404 logger.addHandler(console_handler)
405 logger.addHandler(file_handler)
406 logger.setLevel(args.loglevel)
408 auth = (args.user, args.password)
412 command = args.command
413 prefix_base = args.prefix
414 prefix_len = args.prefixlen
416 auth = (args.user, args.password)
418 xml_template = args.xml
420 test_start_time = time.time()
421 total_build_data_time_counter = 0
422 total_response_time_counter = 0
423 total_number_of_responses_counter = 0
425 if command == "post":
426 post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
429 put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
432 add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
434 elif command == "delete":
435 delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
436 elif command == "delete-all":
437 delete_all_prefixes(odl_ip, port, uri, auth)
438 elif command == "get":
439 get_prefixes(odl_ip, port, uri, auth)
441 total_test_execution_time = time.time() - test_start_time
443 logger.info("Total test execution time: %.3fs", total_test_execution_time)
444 logger.info("Total build data time: %.3fs", total_build_data_time_counter)
445 logger.info("Total response time: %.3fs", total_response_time_counter)
446 logger.info("Total number of response(s): %s", total_number_of_responses_counter)