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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Implementation of low level functionality used in communication with
17 Exception HoneycombError is used in all methods and in all modules with
20 Class HoneycombUtil implements methods used by Honeycomb keywords. They must not
21 be used directly in tests. Use keywords implemented in the module
22 HoneycombAPIKeywords instead.
25 from json import loads, dumps
26 from enum import Enum, unique
28 from robot.api import logger
30 from resources.libraries.python.ssh import SSH
31 from resources.libraries.python.HTTPRequest import HTTPRequest
32 from resources.libraries.python.constants import Constants as Const
36 class DataRepresentation(Enum):
37 """Representation of data sent by PUT and POST requests."""
44 # Headers used in requests. Key - content representation, value - header.
45 HEADERS = {DataRepresentation.NO_DATA:
46 {}, # It must be empty dictionary.
47 DataRepresentation.JSON:
48 {"Content-Type": "application/json",
49 "Accept": "text/plain"},
50 DataRepresentation.XML:
51 {"Content-Type": "application/xml",
52 "Accept": "text/plain"},
53 DataRepresentation.TXT:
54 {"Content-Type": "text/plain",
55 "Accept": "text/plain"}
59 class HoneycombError(Exception):
61 """Exception(s) raised by methods working with Honeycomb.
63 When raising this exception, put this information to the message in this
65 - short description of the encountered problem (parameter msg),
66 - relevant messages if there are any collected, e.g., from caught
67 exception (optional parameter details),
68 - relevant data if there are any collected (optional parameter details).
69 The logging is performed on two levels: 1. error - short description of the
70 problem; 2. debug - detailed information.
73 def __init__(self, msg, details='', enable_logging=True):
74 """Sets the exception message and enables / disables logging.
76 It is not wanted to log errors when using these keywords together
77 with keywords like "Wait until keyword succeeds". So you can disable
78 logging by setting enable_logging to False.
80 :param msg: Message to be displayed and logged.
81 :param enable_logging: When True, logging is enabled, otherwise
84 :type enable_logging: bool
86 super(HoneycombError, self).__init__()
87 self._msg = "{0}: {1}".format(self.__class__.__name__, msg)
88 self._details = details
90 logger.debug(self._details)
93 return repr(self._msg)
99 class HoneycombUtil(object):
100 """Implements low level functionality used in communication with Honeycomb.
102 There are implemented methods to get, put and delete data to/from Honeycomb.
103 They are based on functionality implemented in the module HTTPRequests which
104 uses HTTP requests GET, PUT, POST and DELETE to communicate with Honeycomb.
106 It is possible to PUT the data represented as XML or JSON structures or as
108 Data received in the response of GET are always represented as a JSON
111 There are also two supportive methods implemented:
112 - read_path_from_url_file which reads URL file and returns a path (see
113 docs/honeycomb_url_files.rst).
114 - parse_json_response which parses data from response in JSON representation
115 according to given path.
122 def read_path_from_url_file(url_file):
123 """Read path from *.url file.
125 For more information about *.url file see docs/honeycomb_url_files.rst
126 :param url_file: URL file. The argument contains only the name of file
127 without extension, not the full path.
129 :returns: Requested path.
133 url_file = "{0}/{1}.url".format(Const.RESOURCES_TPL_HC, url_file)
134 with open(url_file) as template:
135 path = template.readline()
139 def find_item(data, path):
140 """Find a data item (single leaf or sub-tree) in data received from
144 The path is a tuple with items navigating to requested data. The items
145 can be strings or tuples:
146 - string item represents a dictionary key in data,
147 - tuple item represents list item in data.
155 "name": "GigabitEthernet0/8/0",
157 "type": "iana-if-type:ethernetCsmacd",
162 "type": "iana-if-type:ethernetCsmacd",
168 path = ("interfaces", ("interface", "name", "local0"), "enabled")
169 This path points to "false".
171 The tuple ("interface", "name", "local0") consists of:
172 index 0 - dictionary key pointing to a list,
173 index 1 - key which identifies an item in the list, it is also marked as
174 the key in corresponding yang file.
177 :param data: Data received from Honeycomb REST API.
178 :param path: Path to data we want to find.
181 :returns: Data represented by path.
182 :rtype: str, dict, or list
183 :raises HoneycombError: If the data has not been found.
186 for path_item in path:
188 if isinstance(path_item, str):
189 data = data[path_item]
190 elif isinstance(path_item, tuple):
191 for data_item in data[path_item[0]]:
192 if data_item[path_item[1]] == path_item[2]:
194 except KeyError as err:
195 raise HoneycombError("Data not found: {0}".format(err))
200 def remove_item(data, path):
201 """Remove a data item (single leaf or sub-tree) in data received from
204 :param data: Data received from Honeycomb REST API.
205 :param path: Path to data we want to remove.
208 :returns: Original data without removed part.
212 origin_data = previous_data = data
214 for path_item in path:
216 if isinstance(path_item, str):
217 data = data[path_item]
218 elif isinstance(path_item, tuple):
219 for data_item in data[path_item[0]]:
220 if data_item[path_item[1]] == path_item[2]:
222 except KeyError as err:
223 logger.debug("Data not found: {0}".format(err))
226 if isinstance(path[-1], str):
227 previous_data.pop(path[-1])
228 elif isinstance(path[-1], tuple):
229 previous_data[path[-1][0]].remove(data)
230 if not previous_data[path[-1][0]]:
231 previous_data.pop(path[-1][0])
236 def set_item_value(data, path, new_value):
237 """Set or change the value (single leaf or sub-tree) in data received
238 from Honeycomb REST API.
240 If the item is not present in the data structure, it is created.
242 :param data: Data received from Honeycomb REST API.
243 :param path: Path to data we want to change or create.
244 :param new_value: The value to be set.
247 :type new_value: str, dict or list
248 :returns: Original data with the new value.
253 for path_item in path[:-1]:
254 if isinstance(path_item, str):
256 data = data[path_item]
259 data = data[path_item]
260 elif isinstance(path_item, tuple):
264 for data_item in data[path_item[0]]:
265 if data_item[path_item[1]] == path_item[2]:
266 data = data[path_item[0]][index]
271 data[path_item[0]].append({path_item[1]: path_item[2]})
272 data = data[path_item[0]][-1]
276 if not path[-1] in data.keys():
279 if isinstance(new_value, list) and isinstance(data[path[-1]], list):
280 for value in new_value:
281 data[path[-1]].append(value)
283 data[path[-1]] = new_value
288 def get_honeycomb_data(node, url_file, path=""):
289 """Retrieve data from Honeycomb according to given URL.
291 :param node: Honeycomb node.
292 :param url_file: URL file. The argument contains only the name of file
293 without extension, not the full path.
294 :param path: Path which is added to the base path to identify the data.
298 :returns: Status code and content of response.
302 base_path = HoneycombUtil.read_path_from_url_file(url_file)
303 path = base_path + path
304 status_code, resp = HTTPRequest.get(node, path)
305 return status_code, loads(resp)
308 def put_honeycomb_data(node, url_file, data, path="",
309 data_representation=DataRepresentation.JSON):
310 """Send configuration data using PUT request and return the status code
311 and response content.
313 :param node: Honeycomb node.
314 :param url_file: URL file. The argument contains only the name of file
315 without extension, not the full path.
316 :param data: Configuration data to be sent to Honeycomb.
317 :param path: Path which is added to the base path to identify the data.
318 :param data_representation: How the data is represented.
321 :type data: dict, str
323 :type data_representation: DataRepresentation
324 :returns: Status code and content of response.
326 :raises HoneycombError: If the given data representation is not defined
331 header = HEADERS[data_representation]
332 except AttributeError as err:
333 raise HoneycombError("Wrong data representation: {0}.".
334 format(data_representation), repr(err))
335 if data_representation == DataRepresentation.JSON:
340 base_path = HoneycombUtil.read_path_from_url_file(url_file)
341 path = base_path + path
343 return HTTPRequest.put(
344 node=node, path=path, headers=header, payload=data)
347 def post_honeycomb_data(node, url_file, data=None,
348 data_representation=DataRepresentation.JSON,
350 """Send a POST request and return the status code and response content.
352 :param node: Honeycomb node.
353 :param url_file: URL file. The argument contains only the name of file
354 without extension, not the full path.
355 :param data: Configuration data to be sent to Honeycomb.
356 :param data_representation: How the data is represented.
357 :param timeout: How long to wait for the server to send data before
361 :type data: dict, str
362 :type data_representation: DataRepresentation
364 :returns: Status code and content of response.
366 :raises HoneycombError: If the given data representation is not defined
371 header = HEADERS[data_representation]
372 except AttributeError as err:
373 raise HoneycombError("Wrong data representation: {0}.".
374 format(data_representation), repr(err))
375 if data_representation == DataRepresentation.JSON:
378 path = HoneycombUtil.read_path_from_url_file(url_file)
379 return HTTPRequest.post(
380 node=node, path=path, headers=header, payload=data, timeout=timeout)
383 def delete_honeycomb_data(node, url_file, path=""):
384 """Delete data from Honeycomb according to given URL.
386 :param node: Honeycomb node.
387 :param url_file: URL file. The argument contains only the name of file
388 without extension, not the full path.
389 :param path: Path which is added to the base path to identify the data.
393 :returns: Status code and content of response.
397 base_path = HoneycombUtil.read_path_from_url_file(url_file)
398 path = base_path + path
399 return HTTPRequest.delete(node, path)
402 def archive_honeycomb_log(node):
403 """Copy honeycomb log file from DUT node to VIRL for archiving.
405 :param node: Honeycomb node.
412 cmd = "cp /var/log/honeycomb/honeycomb.log /scratch/"
414 ssh.exec_command_sudo(cmd)