a57c64b7eeb2fb3d7d7676db263475c2aae0f39c
[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, 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         :element: element to be returned
55
56     Returns:
57         :yield xml_data: requested data by elements as xml data
58     """
59     global total_build_data_time_counter
60
61     routes = md.parse(xml_template)
62
63     routes_node = routes.getElementsByTagName("ipv4-routes")[0]
64     route_node = routes.getElementsByTagName("ipv4-route")[0]
65     routes_node.removeChild(route_node)
66     route_prefix = route_node.getElementsByTagName("prefix")[0]
67     prefix_gap = 2 ** (32 - prefix_len)
68     prefix_index_list = range(count)
69     if element == routes_node.tagName:
70         lines = routes_node.toxml().splitlines()
71         xml_head = lines[0] + "\n"
72         xml_tail = "\n".join(lines[1:])
73     elif element == route_node.tagName:
74         xml_head = ""
75         xml_tail = ""
76         route_node.setAttribute("xmlns", route_node.namespaceURI)
77     else:
78         prefix_index_list = range(0)
79
80     for prefix_index in prefix_index_list:
81         build_data_timestamp = time.time()
82         prefix = prefix_base + prefix_index * prefix_gap
83         prefix_str = str(prefix) + "/" + str(prefix_len)
84         route_prefix.childNodes[0].nodeValue = prefix_str
85         xml_data = route_node.toxml()
86         if prefix_index == 0:
87             xml_data = xml_head + xml_data
88         if prefix_index == len(prefix_index_list) - 1:
89             xml_data = xml_data + xml_tail
90         chunk = prefix_index + 1
91         if not (chunk % 1000):
92             logger.info("... streaming chunk %s (prefix: %s)", chunk, prefix_str)
93         else:
94             logger.debug("...streaming chunk %s (prefix: %s)", chunk, prefix_str)
95         logger.debug("xml data\n%s", xml_data)
96         total_build_data_time_counter += time.time() - build_data_timestamp
97         yield xml_data
98
99
100 def send_request(operation, odl_ip, port, uri, auth, xml_data=None, expect_status_code=200):
101     """Send a http request.
102
103     Args:
104         :operation: GET, POST, PUT, DELETE
105
106         :param odl_ip: controller's ip address or hostname
107
108         :param port: controller's restconf port
109
110         :param uri: URI without /restconf/ to complete URL
111
112         :param auth: authentication credentials
113
114         :param xml_data: list of routes as xml data
115
116     Returns:
117         :returns http response object
118     """
119     global total_response_time_counter
120     global total_number_of_responses_counter
121
122     ses = requests.Session()
123
124     url = _build_url(odl_ip, port, uri)
125     header = {"Content-Type": "application/xml"}
126     req = requests.Request(operation, url, headers=header, data=xml_data, auth=auth)
127     prep = req.prepare()
128     try:
129         send_request_timestamp = time.time()
130         rsp = ses.send(prep, timeout=60)
131         total_response_time_counter += time.time() - send_request_timestamp
132         total_number_of_responses_counter += 1
133     except requests.exceptions.Timeout:
134         logger.error("No response from %s", odl_ip)
135     else:
136         if rsp.status_code == expect_status_code:
137             logger.debug("%s %s", rsp.request, rsp.request.url)
138             logger.debug("Request headers: %s:", rsp.request.headers)
139             logger.debug("Response: %s", rsp.text)
140             logger.debug("%s %s", rsp, rsp.reason)
141         else:
142             logger.error("%s %s", rsp.request, rsp.request.url)
143             logger.error("Request headers: %s:", rsp.request.headers)
144             logger.error("Response: %s", rsp.text)
145             logger.error("%s %s", rsp, rsp.reason)
146         return rsp
147
148
149 def get_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
150                  count=None, xml_template=None):
151     """Send a http GET request for getting all prefixes.
152
153     Args:
154         :param odl_ip: controller's ip address or hostname
155
156         :param port: controller's restconf port
157
158         :param uri: URI without /restconf/ to complete URL
159
160         :param auth: authentication tupple as (user, password)
161
162         :param prefix_base: IP address of the first prefix
163
164         :prefix_len: length of the prefix in bites (specifies the increment as well)
165
166         :param count: number of prefixes to be processed
167
168         :param xml_template: xml template for building the xml data
169
170     Returns:
171         :returns None
172     """
173
174     logger.info("Get all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
175     rsp = send_request("GET", odl_ip, port, uri, auth)
176     if rsp is not None:
177         s = rsp.text
178         s = s.replace("{", "")
179         s = s.replace("}", "")
180         s = s.replace("[", "")
181         s = s.replace("]", "")
182         prefixes = ''
183         prefix_count = 0
184         for item in s.split(","):
185             if "prefix" in item:
186                 prefixes += item + ","
187                 prefix_count += 1
188         prefixes = prefixes[:len(prefixes)-1]
189         logger.debug("prefix_list=%s", prefixes)
190         logger.info("prefix_count=%s", prefix_count)
191
192
193 def post_prefixes(odl_ip, port, uri, auth, prefix_base=None, prefix_len=None,
194                   count=0, xml_template=None):
195     """Send a http POST request for creating a prefix list.
196
197     Args:
198         :param odl_ip: controller's ip address or hostname
199
200         :param port: controller's restconf port
201
202         :param uri: URI without /restconf/ to complete URL
203
204         :param auth: authentication tupple as (user, password)
205
206         :param prefix_base: IP address of the first prefix
207
208         :prefix_len: length of the prefix in bites (specifies the increment as well)
209
210         :param count: number of prefixes to be processed
211
212         :param xml_template: xml template for building the xml data (not used)
213
214     Returns:
215         :returns None
216     """
217     logger.info("Post %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
218                 count, prefix_base, prefix_len, odl_ip, port, uri)
219     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
220     send_request("POST", odl_ip, port, uri, auth, xml_data=xml_stream, expect_status_code=204)
221
222
223 def put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
224                  xml_template=None):
225     """Send a http PUT request for updating the prefix list.
226
227     Args:
228         :param odl_ip: controller's ip address or hostname
229
230         :param port: controller's restconf port
231
232         :param uri: URI without /restconf/ to complete URL
233
234         :param auth: authentication tupple as (user, password)
235
236         :param prefix_base: IP address of the first prefix
237
238         :prefix_len: length of the prefix in bites (specifies the increment as well)
239
240         :param count: number of prefixes to be processed
241
242         :param xml_template: xml template for building the xml data (not used)
243
244     Returns:
245         :returns None
246     """
247     uri_add_prefix = uri + _uri_suffix_ipv4_routes
248     logger.info("Put %s prefix(es) in a single request (starting from %s/%s) into %s:%s/restconf/%s",
249                 count, prefix_base, prefix_len, odl_ip, port, uri_add_prefix)
250     xml_stream = _stream_data(xml_template, prefix_base, prefix_len, count)
251     send_request("PUT", odl_ip, port, uri_add_prefix, auth, xml_data=xml_stream)
252
253
254 def add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
255                  xml_template=None):
256     """Send a consequent http POST request for adding prefixes.
257
258     Args:
259         :param odl_ip: controller's ip address or hostname
260
261         :param port: controller's restconf port
262
263         :param uri: URI without /restconf/ to complete URL
264
265         :param auth: authentication tupple as (user, password)
266
267         :param prefix_base: IP address of the first prefix
268
269         :prefix_len: length of the prefix in bites (specifies the increment as well)
270
271         :param count: number of prefixes to be processed
272
273         :param xml_template: xml template for building the xml data (not used)
274
275     Returns:
276         :returns None
277     """
278     logger.info("Add %s prefixes (starting from %s/%s) into %s:%s/restconf/%s",
279                 count, prefix_base, prefix_len, odl_ip, port, uri)
280     uri_add_prefix = uri + _uri_suffix_ipv4_routes
281     prefix_gap = 2 ** (32 - prefix_len)
282     for prefix_index in range(count):
283         prefix = prefix_base + prefix_index * prefix_gap
284         logger.info("Adding prefix %s/%s to %s:%s/restconf/%s",
285                     prefix, prefix_len, odl_ip, port, uri)
286         xml_stream = _stream_data(xml_template, prefix, prefix_len, 1, "ipv4-route")
287         send_request("POST", odl_ip, port, uri_add_prefix, auth,
288                      xml_data=xml_stream, expect_status_code=204)
289
290
291 def delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
292                     xml_template=None):
293     """Send a http DELETE requests for deleting prefixes.
294
295     Args:
296         :param odl_ip: controller's ip address or hostname
297
298         :param port: controller's restconf port
299
300         :param uri: URI without /restconf/ to complete URL
301
302         :param auth: authentication tupple as (user, password)
303
304         :param prefix_base: IP address of the first prefix
305
306         :prefix_len: length of the prefix in bites (specifies the increment as well)
307
308         :param count: number of prefixes to be processed
309
310         :param xml_template: xml template for building the xml data (not used)
311
312     Returns:
313         :returns None
314     """
315     logger.info("Delete %s prefix(es) (starting from %s/%s) from %s:%s/restconf/%s",
316                 count, prefix_base, prefix_len, odl_ip, port, uri)
317     uri_del_prefix = uri + _uri_suffix_ipv4_routes + _uri_suffix_ipv4_route
318     prefix_gap = 2 ** (32 - prefix_len)
319     for prefix_index in range(count):
320         prefix = prefix_base + prefix_index * prefix_gap
321         logger.info("Deleting prefix %s/%s from %s:%s/restconf/%s",
322                     prefix, prefix_len, odl_ip, port, uri)
323         send_request("DELETE", odl_ip, port,
324                      uri_del_prefix + str(prefix) + "%2F" + str(prefix_len), auth)
325
326
327 def delete_all_prefixes(odl_ip, port, uri, auth, prefix_base=None,
328                         prefix_len=None, count=None, xml_template=None):
329     """Send a http DELETE request for deleting all prefixes.
330
331     Args:
332         :param odl_ip: controller's ip address or hostname
333
334         :param port: controller's restconf port
335
336         :param uri: URI without /restconf/ to complete URL
337
338         :param auth: authentication tupple as (user, password)
339
340         :param prefix_base: IP address of the first prefix (not used)
341
342         :prefix_len: length of the prefix in bites (not used)
343
344         :param count: number of prefixes to be processed (not used)
345
346         :param xml_template: xml template for building the xml data (not used)
347
348     Returns:
349         :returns None
350     """
351     logger.info("Delete all prefixes from %s:%s/restconf/%s", odl_ip, port, uri)
352     uri_del_all_prefixes = uri + _uri_suffix_ipv4_routes
353     send_request("DELETE", odl_ip, port, uri_del_all_prefixes, auth)
354
355
356 _commands = ["post", "put", "add", "delete", "delete-all", "get"]
357 _uri_suffix_ipv4_routes = "bgp-inet:ipv4-routes/"
358 _uri_suffix_ipv4_route = "bgp-inet:ipv4-route/"   # followed by IP address like 1.1.1.1%2F32
359
360 if __name__ == "__main__":
361     parser = argparse.ArgumentParser(description="BGP application peer script")
362     parser.add_argument("--host", type=ipaddr.IPv4Address, default="127.0.0.1",
363                         help="ODL controller IP address")
364     parser.add_argument("--port", default="8181",
365                         help="ODL RESTCONF port")
366     parser.add_argument("--command", choices=_commands, metavar="command",
367                         help="Command to be performed."
368                         "post, put, add, delete, delete-all, get")
369     parser.add_argument("--prefix", type=ipaddr.IPv4Address, default="8.0.1.0",
370                         help="First prefix IP address")
371     parser.add_argument("--prefixlen", type=int, help="Prefix length in bites",
372                         default=28)
373     parser.add_argument("--count", type=int, help="Number of prefixes",
374                         default=1)
375     parser.add_argument("--user", help="Restconf user name", default="admin")
376     parser.add_argument("--password", help="Restconf password", default="admin")
377     parser.add_argument("--uri", help="The uri part of requests",
378                         default="config/bgp-rib:application-rib/example-app-rib/"
379                                 "tables/bgp-types:ipv4-address-family/"
380                                 "bgp-types:unicast-subsequent-address-family/")
381     parser.add_argument("--xml", help="File name of the xml data template",
382                         default="ipv4-routes-template.xml")
383     parser.add_argument("--error", dest="loglevel", action="store_const",
384                         const=logging.ERROR, default=logging.INFO,
385                         help="Set log level to error (default is info)")
386     parser.add_argument("--warning", dest="loglevel", action="store_const",
387                         const=logging.WARNING, default=logging.INFO,
388                         help="Set log level to warning (default is info)")
389     parser.add_argument("--info", dest="loglevel", action="store_const",
390                         const=logging.INFO, default=logging.INFO,
391                         help="Set log level to info (default is info)")
392     parser.add_argument("--debug", dest="loglevel", action="store_const",
393                         const=logging.DEBUG, default=logging.INFO,
394                         help="Set log level to debug (default is info)")
395     parser.add_argument("--logfile", default="bgp_app_peer.log", help="Log file name")
396
397     args = parser.parse_args()
398
399     logger = logging.getLogger("logger")
400     log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
401     console_handler = logging.StreamHandler()
402     file_handler = logging.FileHandler(args.logfile, mode="w")
403     console_handler.setFormatter(log_formatter)
404     file_handler.setFormatter(log_formatter)
405     logger.addHandler(console_handler)
406     logger.addHandler(file_handler)
407     logger.setLevel(args.loglevel)
408
409     auth = (args.user, args.password)
410
411     odl_ip = args.host
412     port = args.port
413     command = args.command
414     prefix_base = args.prefix
415     prefix_len = args.prefixlen
416     count = args.count
417     auth = (args.user, args.password)
418     uri = args.uri
419     xml_template = args.xml
420
421     test_start_time = time.time()
422     total_build_data_time_counter = 0
423     total_response_time_counter = 0
424     total_number_of_responses_counter = 0
425
426     if command == "post":
427         post_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
428                       xml_template)
429     if command == "put":
430         put_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
431                      xml_template)
432     if command == "add":
433         add_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count,
434                      xml_template)
435     elif command == "delete":
436         delete_prefixes(odl_ip, port, uri, auth, prefix_base, prefix_len, count)
437     elif command == "delete-all":
438         delete_all_prefixes(odl_ip, port, uri, auth)
439     elif command == "get":
440         get_prefixes(odl_ip, port, uri, auth)
441
442     total_test_execution_time = time.time() - test_start_time
443
444     logger.info("Total test execution time: %.3fs", total_test_execution_time)
445     logger.info("Total build data time: %.3fs", total_build_data_time_counter)
446     logger.info("Total response time: %.3fs", total_response_time_counter)
447     logger.info("Total number of response(s): %s", total_number_of_responses_counter)
448     file_handler.close()