932e526c0868f3577907eaeaed9c76fd4b7cae04
[integration/test.git] / tools / fastbgp / bgp_app_peer.py
1 """This program performs required BGP application peer operations."""
2
3 # Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
4 #
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
8
9 import requests
10 import ipaddr
11 import argparse
12 import logging
13 import time
14 import xml.dom.minidom as md
15
16
17 __author__ = "Radovan Sajben"
18 __copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
19 __license__ = "Eclipse Public License v1.0"
20 __email__ = "rsajben@cisco.com"
21
22
23 def _build_url(odl_ip, port, uri):
24     """Compose URL from generic IP, port and URI fragment.
25
26     Args:
27         :param odl_ip: controller's ip address or hostname
28
29         :param port: controller's restconf port
30
31         :param uri: URI without /restconf/ to complete URL
32
33     Returns:
34         :returns url: full restconf url corresponding to params
35     """
36
37     url = "http://" + str(odl_ip) + ":" + port + "/restconf/" + uri
38     return url
39
40
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).
44
45     Args:
46         :xml_template: xml template for routes
47
48         :prefix_base: first prefix IP address
49
50         :prefix_len: prefix length in bits
51
52         :count: number of routes to be generated
53
54         :route_key: bool deciding route-key tag existence
55
56         :element: element to be returned
57
58     Returns:
59         :yield xml_data: requested data by elements as xml data
60     """
61     global total_build_data_time_counter
62
63     routes = md.parse(xml_template)
64
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]
69     if route_key:
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:
78         xml_head = ""
79         xml_tail = ""
80         route_node.setAttribute("xmlns", route_node.namespaceURI)
81     else:
82         prefix_index_list = range(0)
83
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
89         if route_key:
90             key.childNodes[0].nodeValue = prefix_str
91         xml_data = route_node.toxml()
92         if prefix_index == 0:
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)
99         else:
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
103         yield xml_data
104
105
106 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
107     """Send a http request.
108
109     Args:
110         :operation: GET, POST, PUT, DELETE
111
112         :param odl_ip: controller's ip address or hostname
113
114         :param port: controller's restconf port
115
116         :param uri: URI without /restconf/ to complete URL
117
118         :param auth: authentication credentials
119
120         :param xml_data: list of routes as xml data
121
122     Returns:
123         :returns http response object
124     """
125     global total_response_time_counter
126     global total_number_of_responses_counter
127
128     ses = requests.Session()
129
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)
133     prep = req.prepare()
134     try:
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)
141     else:
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)
147         else:
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)
152         return rsp
153
154
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.
158
159     Args:
160         :param odl_ip: controller's ip address or hostname
161
162         :param port: controller's restconf port
163
164         :param uri: URI without /restconf/ to complete URL
165
166         :param auth: authentication tupple as (user, password)
167
168         :param prefix_base: IP address of the first prefix
169
170         :prefix_len: length of the prefix in bites (specifies the increment as well)
171
172         :param count: number of prefixes to be processed
173
174         :param xml_template: xml template for building the xml data
175
176     Returns:
177         :returns None
178     """
179
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)
182     if rsp is not None:
183         s = rsp.text
184         s = s.replace("{", "")
185         s = s.replace("}", "")
186         s = s.replace("[", "")
187         s = s.replace("]", "")
188         prefixes = ''
189         prefix_count = 0
190         for item in s.split(","):
191             if "prefix" in item:
192                 prefixes += item + ","
193                 prefix_count += 1
194         prefixes = prefixes[:len(prefixes) - 1]
195         logger.debug("prefix_list=%s", prefixes)
196         logger.info("prefix_count=%s", prefix_count)
197
198
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.
202
203     Args:
204         :param odl_ip: controller's ip address or hostname
205
206         :param port: controller's restconf port
207
208         :param uri: URI without /restconf/ to complete URL
209
210         :param auth: authentication tupple as (user, password)
211
212         :param prefix_base: IP address of the first prefix
213
214         :prefix_len: length of the prefix in bites (specifies the increment as well)
215
216         :param count: number of prefixes to be processed
217
218         :route_key: bool deciding route-key tag existence
219
220         :param xml_template: xml template for building the xml data (not used)
221
222     Returns:
223         :returns None
224     """
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)
229
230
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.
234
235     Args:
236         :param odl_ip: controller's ip address or hostname
237
238         :param port: controller's restconf port
239
240         :param uri: URI without /restconf/ to complete URL
241
242         :param auth: authentication tupple as (user, password)
243
244         :param prefix_base: IP address of the first prefix
245
246         :prefix_len: length of the prefix in bites (specifies the increment as well)
247
248         :param count: number of prefixes to be processed
249
250         :param xml_template: xml template for building the xml data (not used)
251
252     Returns:
253         :returns None
254     """
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)
260
261
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.
265
266     Args:
267         :param odl_ip: controller's ip address or hostname
268
269         :param port: controller's restconf port
270
271         :param uri: URI without /restconf/ to complete URL
272
273         :param auth: authentication tupple as (user, password)
274
275         :param prefix_base: IP address of the first prefix
276
277         :prefix_len: length of the prefix in bites (specifies the increment as well)
278
279         :param count: number of prefixes to be processed
280
281         :param xml_template: xml template for building the xml data (not used)
282
283     Returns:
284         :returns None
285     """
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)
297
298
299 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
300                     xml_template=None):
301     """Send a http DELETE requests for deleting prefixes.
302
303     Args:
304         :param odl_ip: controller's ip address or hostname
305
306         :param port: controller's restconf port
307
308         :param uri: URI without /restconf/ to complete URL
309
310         :param auth: authentication tupple as (user, password)
311
312         :param prefix_base: IP address of the first prefix
313
314         :prefix_len: length of the prefix in bites (specifies the increment as well)
315
316         :param count: number of prefixes to be processed
317
318         :param xml_template: xml template for building the xml data (not used)
319
320     Returns:
321         :returns None
322     """
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)
325     partkey = "/0"
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)
334
335
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.
339
340     Args:
341         :param odl_ip: controller's ip address or hostname
342
343         :param port: controller's restconf port
344
345         :param uri: URI without /restconf/ to complete URL
346
347         :param auth: authentication tupple as (user, password)
348
349         :param prefix_base: IP address of the first prefix (not used)
350
351         :prefix_len: length of the prefix in bites (not used)
352
353         :param count: number of prefixes to be processed (not used)
354
355         :param xml_template: xml template for building the xml data (not used)
356
357     Returns:
358         :returns None
359     """
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)
363
364
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
368
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",
381                         default=28)
382     parser.add_argument("--count", type=int, help="Number of prefixes",
383                         default=1)
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 ...")
406
407     args = parser.parse_args()
408
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)
418
419     auth = (args.user, args.password)
420
421     odl_ip = args.host
422     port = args.port
423     command = args.command
424     prefix_base = args.prefix
425     prefix_len = args.prefixlen
426     count = args.count
427     auth = (args.user, args.password)
428     uri = args.uri
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]
433
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
438
439     if command == "post":
440         post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
441                       route_key, xml_template)
442     if command == "put":
443         put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
444                      route_key, xml_template)
445     if command == "add":
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)
454
455     total_test_execution_time = time.time() - test_start_time
456
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)
461     file_handler.close()