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