Introduce pre-initialize driver layer
[csit.git] / resources / libraries / python / honeycomb / BGP.py
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Keywords to manipulate BGP configuration using Honeycomb REST API."""
15
16 from resources.libraries.python.Constants import Constants as Const
17 from resources.libraries.python.HTTPRequest import HTTPCodes
18 from resources.libraries.python.honeycomb.HoneycombSetup import HoneycombError
19 from resources.libraries.python.honeycomb.HoneycombUtil \
20     import HoneycombUtil as HcUtil
21
22
23 class BGPKeywords(object):
24     """Keywords to manipulate BGP configuration.
25
26     Implements keywords which read configuration and operational data for
27     the BGP feature, and configure BGP parameters using Honeycomb REST API.
28     """
29
30     def __init__(self):
31         """Initializer."""
32         pass
33
34     @staticmethod
35     def _configure_bgp_peer(node, path, data=None):
36         """Send BGP peer configuration data and check the response.
37
38         :param node: Honeycomb node.
39         :param path: Additional path to append to the base BGP config path.
40         :param data: Configuration data to be sent in PUT request.
41         :type node: dict
42         :type path: str
43         :type data: dict
44         :returns: Content of response.
45         :rtype: bytearray
46         :raises HoneycombError: If the status code in response to PUT is not
47             200 = OK or 201 = ACCEPTED.
48         """
49
50         if data is None:
51             status_code, resp = HcUtil. \
52                 delete_honeycomb_data(node, "config_bgp_peer", path)
53         else:
54             status_code, resp = HcUtil.\
55                 put_honeycomb_data(node, "config_bgp_peer", data, path)
56         if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
57             raise HoneycombError(
58                 "The configuration of BGP peer was not successful. "
59                 "Status code: {0}.".format(status_code))
60         return resp
61
62     @staticmethod
63     def _configure_bgp_route(node, path, data=None):
64         """Send BGP route configuration data and check the response.
65
66         :param node: Honeycomb node.
67         :param path: Additional path to append to the base BGP config path.
68         :param data: Configuration data to be sent in PUT request.
69         :type node: dict
70         :type path: str
71         :type data: dict
72         :returns: Content of response.
73         :rtype: bytearray
74         :raises HoneycombError: If the status code in response to PUT is not
75             200 = OK or 201 = ACCEPTED.
76         """
77
78         if data is None:
79             status_code, resp = HcUtil. \
80                 delete_honeycomb_data(node, "config_bgp_route", path)
81         else:
82             status_code, resp = HcUtil. \
83                 put_honeycomb_data(node, "config_bgp_route", data, path)
84         if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
85             raise HoneycombError(
86                 "The configuration of BGP route was not successful. "
87                 "Status code: {0}.".format(status_code))
88         return resp
89
90     @staticmethod
91     def get_full_bgp_configuration(node):
92         """Get BGP configuration from the node.
93
94         :param node: Honeycomb node.
95         :type node: dict
96         :returns: BGP configuration data.
97         :rtype: dict
98         :raises HoneycombError: If the status code in response is not 200 = OK.
99         """
100
101         status_code, resp = HcUtil. \
102             get_honeycomb_data(node, "config_bgp_peer")
103         if status_code != HTTPCodes.OK:
104             raise HoneycombError(
105                 "Not possible to get configuration information about BGP."
106                 " Status code: {0}.".format(status_code))
107         return resp
108
109     @staticmethod
110     def get_bgp_peer(node, address, datastore='config'):
111         """Get BGP configuration of the specified peer from the node.
112
113         :param node: Honeycomb node.
114         :param address: IP address of the peer.
115         :param datastore: Get data from config or operational datastore.
116         :type node: dict
117         :type address: str
118         :type datastore: str
119         :returns: BGP peer configuration data.
120         :rtype: dict
121         :raises HoneycombError: If the status code in response is not 200 = OK.
122         """
123
124         path = "bgp-openconfig-extensions:neighbors/" \
125                "neighbor/{0}".format(address)
126         if datastore != "operational":
127             url = "config_bgp_peer"
128         else:
129             url = "oper_bgp"
130             path = "peer/bgp:%2F%2F{0}".format(address)
131         status_code, resp = HcUtil. \
132             get_honeycomb_data(node, url, path)
133         if status_code != HTTPCodes.OK:
134             raise HoneycombError(
135                 "Not possible to get configuration information about the BGP"
136                 " peer. Status code: {0}.".format(status_code))
137         return resp
138
139     @staticmethod
140     def add_bgp_peer(node, address, data):
141         """Configure a BGP peer on the node.
142
143         :param node: Honeycomb node.
144         :param address: IP address of the peer.
145         :param data: Peer configuration data.
146         :type node: dict
147         :type address: str
148         :type data: dict
149         :returns: Content of response.
150         :rtype: bytearray
151         """
152
153         path = "bgp-openconfig-extensions:neighbors/neighbor/{address}".format(
154             address=address)
155         return BGPKeywords._configure_bgp_peer(node, path, data)
156
157     @staticmethod
158     def remove_bgp_peer(node, address):
159         """Remove a BGP peer from the configuration.
160
161         :param node: Honeycomb node.
162         :param address: IP address of the peer.
163         :type node: dict
164         :type address: str
165         :returns: Content of response.
166         :rtype: bytearray
167         """
168
169         path = "bgp-openconfig-extensions:neighbors/neighbor/{address}".format(
170             address=address)
171         return BGPKeywords._configure_bgp_peer(node, path)
172
173     @staticmethod
174     def configure_bgp_route(node, peer_address, data, route_address,
175                             index, ip_version):
176         """Configure a route for the BGP peer specified by peer IP address.
177
178         :param node: Honeycomb node.
179         :param peer_address: IP address of the BGP peer.
180         :param data: Route configuration data.
181         :param route_address: IP address of the route.
182         :param index: Index number of the route within specified peer.
183         :param ip_version: IP protocol version. ipv4 or ipv6
184         :type node: dict
185         :type peer_address: str
186         :type data: dict
187         :type route_address: str
188         :type index: int
189         :type ip_version: str
190         :returns: Content of response.
191         :rtype: bytearray
192         """
193
194         route_address = route_address.replace("/", "%2F")
195
196         if ip_version.lower() == "ipv4":
197             path = "{0}/tables/bgp-types:ipv4-address-family/" \
198                    "bgp-types:unicast-subsequent-address-family/" \
199                    "bgp-inet:ipv4-routes/ipv4-route/{1}/{2}" \
200                 .format(peer_address, route_address, index)
201         else:
202             path = "{0}/tables/bgp-types:ipv6-address-family/" \
203                    "bgp-types:unicast-subsequent-address-family/" \
204                    "bgp-inet:ipv6-routes/ipv6-route/{1}/{2}" \
205                 .format(peer_address, route_address, index)
206
207         return BGPKeywords._configure_bgp_route(node, path, data)
208
209     @staticmethod
210     def get_bgp_route(node, peer_address, route_address, index, ip_version):
211         """Get all BGP peers from operational data.
212
213         :param node: Honeycomb node.
214         :param peer_address: IP address of the BGP peer.
215         :param route_address: IP address of the route.
216         :param index: Index number of the route within specified peer.
217         :param ip_version: IP protocol version. ipv4 or ipv6
218         :type node: dict
219         :type peer_address: str
220         :type route_address: str
221         :type index: int
222         :type ip_version: str
223         :returns: Content of response.
224         :rtype: bytearray
225         :raises HoneycombError: If the status code in response is not 200 = OK.
226         """
227
228         route_address = route_address.replace("/", "%2F")
229
230         if ip_version.lower() == "ipv4":
231             path = "{0}/tables/bgp-types:ipv4-address-family/" \
232                    "bgp-types:unicast-subsequent-address-family/" \
233                    "bgp-inet:ipv4-routes/ipv4-route/{1}/{2}" \
234                 .format(peer_address, route_address, index)
235         else:
236             path = "{0}/tables/bgp-types:ipv6-address-family/" \
237                    "bgp-types:unicast-subsequent-address-family/" \
238                    "bgp-inet:ipv6-routes/ipv6-route/{1}/{2}" \
239                 .format(peer_address, route_address, index)
240         status_code, resp = HcUtil. \
241             get_honeycomb_data(node, "config_bgp_route", path)
242         if status_code != HTTPCodes.OK:
243             raise HoneycombError(
244                 "Not possible to get configuration information about the BGP"
245                 " route. Status code: {0}.".format(status_code))
246
247         return resp
248
249     @staticmethod
250     def get_all_peer_routes(node, peer_address, ip_version):
251         """Get all configured routes for the given BGP peer.
252
253         :param node: Honeycomb node.
254         :param peer_address: IP address of the peer.
255         :param ip_version: IP protocol version. ipv4 or ipv6
256         :type node: dict
257         :type peer_address: str
258         :type ip_version: str
259         :returns: Content of response.
260         :rtype: bytearray
261         :raises HoneycombError: If the status code in response is not 200 = OK.
262         """
263
264         if ip_version.lower() == "ipv4":
265             path = "{0}/tables/bgp-types:ipv4-address-family/" \
266                    "bgp-types:unicast-subsequent-address-family/" \
267                    "bgp-inet:ipv4-routes".format(peer_address)
268         else:
269             path = "{0}/tables/bgp-types:ipv6-address-family/" \
270                    "bgp-types:unicast-subsequent-address-family/" \
271                    "bgp-inet:ipv6-routes".format(peer_address)
272         status_code, resp = HcUtil. \
273             get_honeycomb_data(node, "config_bgp_route", path)
274         if status_code != HTTPCodes.OK:
275             raise HoneycombError(
276                 "Not possible to get configuration information about BGP"
277                 " routes. Status code: {0}.".format(status_code))
278
279         return resp
280
281     @staticmethod
282     def remove_bgp_route(node, peer_address, route_address, index, ip_version):
283         """Remove the specified BGP route from configuration.
284
285         :param node: Honeycomb node.
286         :param peer_address: IP address of the BGP peer.
287         :param route_address: IP address of the route.
288         :param index: Index number of the route within specified peer.
289         :param ip_version: IP protocol version. ipv4 or ipv6
290         :type node: dict
291         :type peer_address: str
292         :type route_address: str
293         :type index: int
294         :type ip_version: str
295         :returns: Content of response.
296         :rtype: bytearray
297         """
298
299         route_address = route_address.replace("/", "%2F")
300
301         if ip_version.lower() == "ipv4":
302             path = "{0}/tables/bgp-types:ipv4-address-family/" \
303                    "bgp-types:unicast-subsequent-address-family/" \
304                    "bgp-inet:ipv4-routes/ipv4-route/{1}/{2}" \
305                 .format(peer_address, route_address, index)
306         else:
307             path = "{0}/tables/bgp-types:ipv6-address-family/" \
308                    "bgp-types:unicast-subsequent-address-family/" \
309                    "bgp-inet:ipv6-routes/ipv6-route/{1}/{2}" \
310                 .format(peer_address, route_address, index)
311
312         return BGPKeywords._configure_bgp_route(node, path)
313
314     @staticmethod
315     def get_bgp_local_rib(node):
316         """Get local RIB table from the Honeycomb node.
317
318         :param node: Honeycomb node.
319         :type node: dict
320         :returns: RIB operational data.
321         :rtype: dict
322         :raises HoneycombError: If the status code in response is not 200 = OK.
323         """
324
325         path = "loc-rib"
326
327         status_code, resp = HcUtil. \
328             get_honeycomb_data(node, "oper_bgp", path)
329
330         if status_code != HTTPCodes.OK:
331             raise HoneycombError(
332                 "Not possible to get operational data from BGP local RIB."
333                 " Status code: {0}.".format(status_code))
334
335         return resp
336
337     @staticmethod
338     def configure_bgp_base(node, ip_address, port, as_number):
339         """Modify BGP config file. Requires a restart of Honeycomb to take
340         effect.
341
342         :param node: Honeycomb node.
343         :param ip_address: BGP peer identifier/binding address.
344         :param port: BGP binding port.
345         :param as_number: Autonomous System ID number.
346         :type node: dict
347         :type ip_address: str
348         :type port: int
349         :type as_number: int
350         :raises HoneycombError: If modifying the configuration fails.
351         """
352
353         from resources.libraries.python.ssh import SSH
354
355         config = {
356             '\\"bgp-binding-address\\"': '\\"{0}\\"'.format(ip_address),
357             '\\"bgp-port\\"': port,
358             '\\"bgp-as-number\\"': as_number}
359
360         path = "{0}/config/bgp.json".format(Const.REMOTE_HC_DIR)
361
362         for key, value in config.items():
363             find = key
364             replace = '"{0}": "{1}",'.format(key, value)
365
366             argument = '"/{0}/c\\ {1}"'.format(find, replace)
367             command = "sed -i {0} {1}".format(argument, path)
368
369             ssh = SSH()
370             ssh.connect(node)
371             (ret_code, _, stderr) = ssh.exec_command_sudo(command)
372             if ret_code != 0:
373                 raise HoneycombError("Failed to modify configuration on "
374                                      "node {0}, {1}".format(node, stderr))
375
376     @staticmethod
377     def compare_rib_tables(data, ref):
378         """Compare provided RIB table with reference. All reference entries must
379         be present in data. Data entries not present in reference are ignored.
380
381         :param data: Data from Honeycomb node.
382         :param ref: Reference data to compare against.
383         :type data: dict
384         :type ref: dict
385         :raises HoneycombError: If the tables do not match.
386         """
387
388         # Remove runtime attributes from data
389         for item in data:
390             item.pop("attributes", "")
391
392         for item in ref:
393             if item not in data:
394                 raise HoneycombError(
395                     "RIB entry {0} not found in operational data {1}."
396                     .format(item, data))