Add optional args to traffic script arg parser
[csit.git] / resources / libraries / python / HoneycombAPIKeywords.py
1 # Copyright (c) 2016 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 used with Honeycomb.
15
16 There are implemented keywords which work with:
17 - Honeycomb operations
18 - VPP Interfaces
19 - Bridge domains
20
21 The keywords make possible to put and get configuration data and to get
22 operational data.
23 """
24
25 from json import dumps
26
27 from robot.api import logger
28
29 from resources.libraries.python.HTTPRequest import HTTPCodes
30 from resources.libraries.python.HoneycombSetup import HoneycombError
31 from resources.libraries.python.HoneycombUtil import HoneycombUtil as HcUtil
32 from resources.libraries.python.HoneycombUtil import DataRepresentation
33
34
35 class OperationsKeywords(object):
36     """Keywords which perform "operations" in Honeycomb.
37
38     The keywords in this class are not a part of a specific area in Honeycomb,
39     e.g.: interfaces or bridge domains, but they perform "operations" in any
40     area of Honeycomb.
41     """
42
43     def __init__(self):
44         pass
45
46     @staticmethod
47     def poll_oper_data(node):
48         """Poll operational data.
49
50         You can use this keyword when you configure something in Honeycomb and
51         you want configuration data to make effect immediately, e.g.:
52
53         | | Create Bridge Domain | ....
54         | | Add Bridge Domain To Interface | ....
55         | | Poll Oper Data | ....
56         | | ${br}= | Get Oper Info About Bridge Domain | ....
57
58         ..note:: This is not very reliable way how to poll operational data.
59         This keyword is only temporary workaround and will be removed when this
60         problem is solved in Honeycomb.
61         :param node: Honeycomb node.
62         :type: dict
63         :raises HoneycombError: If it is not possible to poll operational data.
64         """
65
66         status_code, _ = HcUtil.\
67             post_honeycomb_data(node, "poll_oper_data", data='',
68                                 data_representation=DataRepresentation.NO_DATA,
69                                 timeout=30)
70         if status_code != HTTPCodes.OK:
71             raise HoneycombError("It was not possible to poll operational data "
72                                  "on node {0}.".format(node['host']))
73
74
75 class InterfaceKeywords(object):
76     """Keywords for Interface manipulation.
77
78     Implements keywords which get configuration and operational data about
79     vpp interfaces and set the interface's parameters using Honeycomb REST API.
80     """
81
82     def __init__(self):
83         pass
84
85     @staticmethod
86     def _configure_interface(node, interface, data,
87                              data_representation=DataRepresentation.JSON):
88         """Send interface configuration data and check the response.
89
90         :param node: Honeycomb node.
91         :param interface: The name of interface.
92         :param data: Configuration data to be sent in PUT request.
93         :param data_representation: How the data is represented.
94         :type node: dict
95         :type interface: str
96         :type data: str
97         :type data_representation: DataRepresentation
98         :return: Content of response.
99         :rtype: bytearray
100         :raises HoneycombError: If the status code in response on PUT is not
101         200 = OK.
102         """
103
104         status_code, resp = HcUtil.\
105             put_honeycomb_data(node, "config_vpp_interfaces", data,
106                                data_representation=data_representation)
107         if status_code != HTTPCodes.OK:
108             raise HoneycombError(
109                 "The configuration of interface '{0}' was not successful. "
110                 "Status code: {1}.".format(interface, status_code))
111         return resp
112
113     @staticmethod
114     def get_all_interfaces_cfg_data(node):
115         """Get configuration data about all interfaces from Honeycomb.
116
117         :param node: Honeycomb node.
118         :type node: dict
119         :return: Configuration data about all interfaces from Honeycomb.
120         :rtype: list
121         :raises HoneycombError: If it is not possible to get configuration data.
122         """
123
124         status_code, resp = HcUtil.get_honeycomb_data(node,
125                                                       "config_vpp_interfaces")
126         if status_code != HTTPCodes.OK:
127             raise HoneycombError(
128                 "Not possible to get configuration information about the "
129                 "interfaces. Status code: {0}.".format(status_code))
130         try:
131             intf = HcUtil.parse_json_response(resp, ("interfaces", "interface"))
132             return intf
133         except KeyError:
134             return []
135
136     @staticmethod
137     def get_interface_cfg_info(node, interface):
138         """Get configuration data about the given interface from Honeycomb.
139
140         :param node: Honeycomb node.
141         :param interface: The name of interface.
142         :type node: dict
143         :type interface: str
144         :return: Configuration data about the given interface from Honeycomb.
145         :rtype: dict
146         """
147
148         intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node)
149         for intf in intfs:
150             if intf["name"] == interface:
151                 return intf
152         return {}
153
154     @staticmethod
155     def get_all_interfaces_oper_info(node):
156         """Get operational data about all interfaces from Honeycomb.
157
158         :param node: Honeycomb node.
159         :type node: dict
160         :return: Operational data about all interfaces from Honeycomb.
161         :rtype: list
162         :raises HoneycombError: If it is not possible to get operational data.
163         """
164
165         status_code, resp = HcUtil.get_honeycomb_data(node,
166                                                       "oper_vpp_interfaces")
167         if status_code != HTTPCodes.OK:
168             raise HoneycombError(
169                 "Not possible to get operational information about the "
170                 "interfaces. Status code: {0}.".format(status_code))
171         try:
172             intf = HcUtil.parse_json_response(resp, ("interfaces-state",
173                                                      "interface"))
174             return intf
175         except KeyError:
176             return []
177
178     @staticmethod
179     def get_interface_oper_info(node, interface):
180         """Get operational data about the given interface from Honeycomb.
181
182         :param node: Honeycomb node.
183         :param interface: The name of interface.
184         :type node: dict
185         :type interface: str
186         :return: Operational data about the given interface from Honeycomb.
187         :rtype: dict
188         """
189
190         intfs = InterfaceKeywords.get_all_interfaces_oper_info(node)
191         for intf in intfs:
192             if intf["name"] == interface:
193                 return intf
194         return {}
195
196     @staticmethod
197     def set_interface_state(node, interface, state="up"):
198         """Set VPP interface state.
199
200         The keyword changes the administration state of interface to up or down
201         depending on the parameter "state".
202
203         :param node: Honeycomb node.
204         :param interface: The name of interface.
205         :param state: The requested state, only "up" and "down" are valid
206         values.
207         :type node: dict
208         :type interface: str
209         :type state: str
210         :return: Content of response.
211         :rtype: bytearray
212         :raises KeyError: If the argument "state" is nor "up" or "down".
213         :raises HoneycombError: If the interface is not present on the node.
214         """
215
216         intf_state = {"up": "true",
217                       "down": "false"}
218         intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node)
219         for intf in intfs:
220             if intf["name"] == interface:
221                 intf["enabled"] = intf_state[state.lower()]
222                 new_intf = {"interfaces": {"interface": intfs}}
223                 return InterfaceKeywords._configure_interface(node, interface,
224                                                               dumps(new_intf))
225         raise HoneycombError("The interface '{0}' is not present on node "
226                              "'{1}'.".format(interface, node['host']))
227
228     @staticmethod
229     def set_interface_up(node, interface):
230         """Set the administration state of VPP interface to up.
231
232         :param node: Honeycomb node.
233         :param interface: The name of interface.
234         :type node: dict
235         :type interface: str
236         :return: Content of response
237         :rtype: bytearray
238         """
239
240         return InterfaceKeywords.set_interface_state(node, interface, "up")
241
242     @staticmethod
243     def set_interface_down(node, interface):
244         """Set the administration state of VPP interface to down.
245
246         :param node: Honeycomb node.
247         :param interface: The name of interface.
248         :type node: dict
249         :type interface: str
250         :return: Content of response.
251         :rtype: bytearray
252         """
253
254         return InterfaceKeywords.set_interface_state(node, interface, "down")
255
256     @staticmethod
257     def add_bridge_domain_to_interface(node, interface, bd_name,
258                                        split_horizon_group=None, bvi=None):
259         """Add a new bridge domain to an interface and set its parameters.
260
261         :param node: Honeycomb node.
262         :param interface: The name of interface.
263         :param bd_name: Bridge domain name.
264         :param split_horizon_group: Split-horizon group name.
265         :param bvi: The bridged virtual interface.
266         :type node: dict
267         :type interface: str
268         :type bd_name: str
269         :type split_horizon_group: str
270         :type bvi: str
271         :return: Content of response.
272         :rtype: bytearray
273         :raises HoneycombError: If the interface is not present on the node.
274         """
275
276         intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node)
277         v3po_l2 = {"bridge-domain": str(bd_name)}
278         if split_horizon_group:
279             v3po_l2["split-horizon-group"] = str(split_horizon_group)
280         if bvi:
281             v3po_l2["bridged-virtual-interface"] = str(bvi)
282         for intf in intfs:
283             if intf["name"] == interface:
284                 intf["v3po:l2"] = v3po_l2
285                 new_intf = {"interfaces": {"interface": intfs}}
286                 return InterfaceKeywords._configure_interface(node, interface,
287                                                               dumps(new_intf))
288         raise HoneycombError("The interface '{0}' is not present on node "
289                              "'{1}'.".format(interface, node['host']))
290
291
292 class BridgeDomainKeywords(object):
293     """Keywords for Bridge domain manipulation.
294
295     Implements keywords which get configuration and operational data about
296     bridge domains and put the bridge domains' parameters using Honeycomb REST
297     API.
298     """
299
300     def __init__(self):
301         pass
302
303     @staticmethod
304     def _create_json_bridge_domain_info(name, **kwargs):
305         """Generate bridge domain information in the structure as it is expected
306         by Honeycomb.
307
308         The generated data structure is as follows:
309         {
310             "bridge-domains": {
311                 "bridge-domain": [
312                     {
313                         "name": "bd_name",
314                         "flood": "false",
315                         "forward": "false",
316                         "learn": "false",
317                         "unknown-unicast-flood": "false",
318                         "arp-termination": "false"
319                     }
320                 ]
321             }
322         }
323
324         :param name: The name of new bridge-domain.
325         :param kwargs: named arguments:
326             flood (bool): If True, flooding is enabled.
327             forward (bool): If True, packet forwarding is enabled.
328             learn (bool): If True, learning is enabled.
329             uu_flood (bool): If True, unknown unicast flooding is enabled.
330             arp_termination (bool): If True, ARP termination is enabled.
331         :type name: str
332         :type kwargs: dict
333         :return: Bridge domain information in format suitable for Honeycomb.
334         :rtype: dict
335         :raises KeyError: If at least one of kwargs items is missing.
336         """
337
338         brd_info = {
339             "bridge-domains": {
340                 "bridge-domain": [
341                     {"name": name,
342                      "flood": str(kwargs["flood"]).lower(),
343                      "forward": str(kwargs["forward"]).lower(),
344                      "learn": str(kwargs["learn"]).lower(),
345                      "unknown-unicast-flood": str(kwargs["uu_flood"]).lower(),
346                      "arp-termination": str(kwargs["arp_termination"]).lower()},
347                 ]
348             }
349         }
350
351         return brd_info
352
353     @staticmethod
354     def create_bridge_domain(node, name, flood=True, forward=True, learn=True,
355                              uu_flood=True, arp_termination=False):
356         """Create a bridge domain using Honeycomb.
357
358         This keyword adds a new bridge domain to the list of bridge domains and
359         sets its parameters. The existing bridge domains are untouched.
360         :param node: Node with Honeycomb where the bridge domain should be
361         created.
362         :param name: The name of new bridge-domain.
363         :param flood: If True, flooding is enabled.
364         :param forward: If True, packet forwarding is enabled.
365         :param learn: If True, learning is enabled.
366         :param uu_flood: If True, unknown unicast flooding is enabled.
367         :param arp_termination: If True, ARP termination is enabled.
368         :type node: dict
369         :type name: str
370         :type flood: bool
371         :type forward: bool
372         :type learn: bool
373         :type uu_flood: bool
374         :type arp_termination: bool
375         :raises HoneycombError: If the bridge domain already exists or it has
376         not been created.
377         """
378
379         existing_brds = BridgeDomainKeywords.\
380             get_all_bds_cfg_data(node, ignore_404=True)
381
382         for brd in existing_brds:
383             if brd["name"] == name:
384                 raise HoneycombError("Bridge domain {0} already exists.".
385                                      format(name))
386
387         brd_info = BridgeDomainKeywords._create_json_bridge_domain_info(
388             name, flood=flood, forward=forward, learn=learn, uu_flood=uu_flood,
389             arp_termination=arp_termination)
390         for brd in existing_brds:
391             brd_info["bridge-domains"]["bridge-domain"].append(brd)
392
393         status_code, _ = HcUtil.put_honeycomb_data(node, "config_bridge_domain",
394                                                    dumps(brd_info))
395         if status_code != HTTPCodes.OK:
396             raise HoneycombError(
397                 "Bridge domain {0} was not created. "
398                 "Status code: {01}.".format(name, status_code))
399
400     @staticmethod
401     def get_all_bds_oper_data(node):
402         """Get operational data about all bridge domains from Honeycomb.
403
404         :param node: Honeycomb node.
405         :type node: dict
406         :return: Operational data about all bridge domains from Honeycomb.
407         :rtype: list
408         :raises HoneycombError: If it is not possible to get information about
409         the bridge domains.
410         """
411
412         status_code, resp = HcUtil.get_honeycomb_data(node,
413                                                       "oper_bridge_domains")
414         if status_code != HTTPCodes.OK:
415             raise HoneycombError(
416                 "Not possible to get information about the bridge domains. "
417                 "Status code: {0}.".format(status_code))
418         try:
419             br_domains = HcUtil.parse_json_response(resp, ("bridge-domains",
420                                                            "bridge-domain"))
421         except KeyError:
422             return []
423         return br_domains
424
425     @staticmethod
426     def get_bd_oper_data(node, name):
427         """Get operational data about the given bridge domain from Honeycomb.
428
429         :param node: Honeycomb node.
430         :param name: The name of bridge domain.
431         :type node: dict
432         :type name: str
433         :return: Operational data about the given bridge domain from Honeycomb.
434         :rtype: dict
435         """
436
437         br_domains = BridgeDomainKeywords.get_all_bds_oper_data(node)
438         for br_domain in br_domains:
439             if br_domain["name"] == name:
440                 br_domain["name"] = br_domain["name"]
441                 return br_domain
442         return {}
443
444     @staticmethod
445     def get_all_bds_cfg_data(node, ignore_404=False):
446         """Get configuration data about all bridge domains from Honeycomb.
447
448         :param node: Honeycomb node.
449         :param ignore_404: If True, the error 404 is ignored.
450         :type node: dict
451         :type ignore_404: bool
452         :return: Configuration data about all bridge domains from Honeycomb.
453         :rtype: list
454         :raises HoneycombError: If it is not possible to get information about
455         the bridge domains.
456         """
457
458         status_code, resp = HcUtil.get_honeycomb_data(node,
459                                                       "config_bridge_domain")
460         if status_code != HTTPCodes.OK:
461             if ignore_404 and status_code == HTTPCodes.NOT_FOUND:
462                 br_domains = list()
463                 logger.debug("Error 404 ignored")
464             else:
465                 raise HoneycombError(
466                     "Not possible to get information about the bridge domains. "
467                     "Status code: {0}.".format(status_code))
468         else:
469             try:
470                 br_domains = HcUtil.parse_json_response(resp, ("bridge-domains",
471                                                                "bridge-domain"))
472             except KeyError:
473                 return []
474         return br_domains
475
476     @staticmethod
477     def get_bd_cfg_data(node, name):
478         """Get configuration data about the given bridge domain from Honeycomb.
479
480         :param node: Honeycomb node.
481         :param name: The name of bridge domain.
482         :type node: dict
483         :type name: str
484         :return: Configuration data about the given bridge domain from
485         Honeycomb.
486         :rtype: dict
487         """
488
489         br_domains = BridgeDomainKeywords.get_all_bds_cfg_data(node)
490         for br_domain in br_domains:
491             if br_domain["name"] == name:
492                 return br_domain
493         return {}
494
495     @staticmethod
496     def delete_all_bridge_domains(node):
497         """Delete all bridge domains on Honeycomb node.
498
499         :param node: Honeycomb node.
500         :type node: dict
501         :return: Response from DELETE request.
502         :rtype: str
503         :raises HoneycombError: If it is not possible to delete all bridge
504         domains.
505         """
506
507         status_code, resp = HcUtil.delete_honeycomb_data(node,
508                                                          "config_bridge_domain")
509         if status_code != HTTPCodes.OK:
510             raise HoneycombError(
511                 "Not possible to delete all bridge domains. "
512                 "Status code: {0}.".format(status_code))
513         return resp
514
515     @staticmethod
516     def remove_bridge_domain(node, name):
517         """Remove one bridge domain from Honeycomb.
518
519         :param node: Honeycomb node.
520         :param name: Name of the bridge domain to be removed.
521         :type node: dict
522         :type name: str
523         :return: True if the bridge domain was removed.
524         :rtype: bool
525         :raises HoneycombError: If it is not possible to remove the bridge
526         domain.
527         """
528
529         br_domains = BridgeDomainKeywords.get_all_bds_cfg_data(node)
530         for br_domain in br_domains:
531             if br_domain["name"] == name:
532                 br_domains.remove(br_domain)
533                 brd_info = {"bridge-domains": {"bridge-domain": br_domains}}
534                 status_code, _ = HcUtil.put_honeycomb_data(
535                     node, "config_bridge_domain", dumps(brd_info))
536                 if status_code != HTTPCodes.OK:
537                     raise HoneycombError(
538                         "Bridge domain '{0}' was not deleted. "
539                         "Status code: {1}.".format(name, status_code))
540                 return True
541
542         raise HoneycombError("Not possible to delete bridge domain '{0}'. The "
543                              "bridge domain was not found".format(name))