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.HTTPRequest import HTTPRequest
31 from resources.libraries.python.constants import Constants as Const
35 class DataRepresentation(Enum):
36 """Representation of data sent by PUT and POST requests."""
43 # Headers used in requests. Key - content representation, value - header.
44 HEADERS = {DataRepresentation.NO_DATA:
45 {}, # It must be empty dictionary.
46 DataRepresentation.JSON:
47 {"Content-Type": "application/json",
48 "Accept": "text/plain"},
49 DataRepresentation.XML:
50 {"Content-Type": "application/xml",
51 "Accept": "text/plain"},
52 DataRepresentation.TXT:
53 {"Content-Type": "text/plain",
54 "Accept": "text/plain"}
58 class HoneycombError(Exception):
60 """Exception(s) raised by methods working with Honeycomb.
62 When raising this exception, put this information to the message in this
64 - short description of the encountered problem (parameter msg),
65 - relevant messages if there are any collected, e.g., from caught
66 exception (optional parameter details),
67 - relevant data if there are any collected (optional parameter details).
68 The logging is performed on two levels: 1. error - short description of the
69 problem; 2. debug - detailed information.
72 def __init__(self, msg, details='', enable_logging=True):
73 """Sets the exception message and enables / disables logging.
75 It is not wanted to log errors when using these keywords together
76 with keywords like "Wait until keyword succeeds". So you can disable
77 logging by setting enable_logging to False.
79 :param msg: Message to be displayed and logged.
80 :param enable_logging: When True, logging is enabled, otherwise
83 :type enable_logging: bool
85 super(HoneycombError, self).__init__()
86 self._msg = "{0}: {1}".format(self.__class__.__name__, msg)
87 self._details = details
89 logger.debug(self._details)
92 return repr(self._msg)
98 class HoneycombUtil(object):
99 """Implements low level functionality used in communication with Honeycomb.
101 There are implemented methods to get, put and delete data to/from Honeycomb.
102 They are based on functionality implemented in the module HTTPRequests which
103 uses HTTP requests GET, PUT, POST and DELETE to communicate with Honeycomb.
105 It is possible to PUT the data represented as XML or JSON structures or as
107 Data received in the response of GET are always represented as a JSON
110 There are also two supportive methods implemented:
111 - read_path_from_url_file which reads URL file and returns a path (see
112 docs/honeycomb_url_files.rst).
113 - parse_json_response which parses data from response in JSON representation
114 according to given path.
121 def read_path_from_url_file(url_file):
122 """Read path from *.url file.
124 For more information about *.url file see docs/honeycomb_url_files.rst
125 :param url_file: URL file. The argument contains only the name of file
126 without extension, not the full path.
128 :returns: Requested path.
132 url_file = "{0}/{1}.url".format(Const.RESOURCES_TPL_HC, url_file)
133 with open(url_file) as template:
134 path = template.readline()
138 def find_item(data, path):
139 """Find a data item (single leaf or sub-tree) in data received from
143 The path is a tuple with items navigating to requested data. The items
144 can be strings or tuples:
145 - string item represents a dictionary key in data,
146 - tuple item represents list item in data.
154 "name": "GigabitEthernet0/8/0",
156 "type": "iana-if-type:ethernetCsmacd",
161 "type": "iana-if-type:ethernetCsmacd",
167 path = ("interfaces", ("interface", "name", "local0"), "enabled")
168 This path points to "false".
170 The tuple ("interface", "name", "local0") consists of:
171 index 0 - dictionary key pointing to a list,
172 index 1 - key which identifies an item in the list, it is also marked as
173 the key in corresponding yang file.
176 :param data: Data received from Honeycomb REST API.
177 :param path: Path to data we want to find.
180 :returns: Data represented by path.
181 :rtype: str, dict, or list
182 :raises HoneycombError: If the data has not been found.
185 for path_item in path:
187 if isinstance(path_item, str):
188 data = data[path_item]
189 elif isinstance(path_item, tuple):
190 for data_item in data[path_item[0]]:
191 if data_item[path_item[1]] == path_item[2]:
193 except KeyError as err:
194 raise HoneycombError("Data not found: {0}".format(err))
199 def remove_item(data, path):
200 """Remove a data item (single leaf or sub-tree) in data received from
203 :param data: Data received from Honeycomb REST API.
204 :param path: Path to data we want to remove.
207 :returns: Original data without removed part.
211 origin_data = previous_data = data
213 for path_item in path:
215 if isinstance(path_item, str):
216 data = data[path_item]
217 elif isinstance(path_item, tuple):
218 for data_item in data[path_item[0]]:
219 if data_item[path_item[1]] == path_item[2]:
221 except KeyError as err:
222 logger.debug("Data not found: {0}".format(err))
225 if isinstance(path[-1], str):
226 previous_data.pop(path[-1])
227 elif isinstance(path[-1], tuple):
228 previous_data[path[-1][0]].remove(data)
229 if not previous_data[path[-1][0]]:
230 previous_data.pop(path[-1][0])
235 def set_item_value(data, path, new_value):
236 """Set or change the value (single leaf or sub-tree) in data received
237 from Honeycomb REST API.
239 If the item is not present in the data structure, it is created.
241 :param data: Data received from Honeycomb REST API.
242 :param path: Path to data we want to change or create.
243 :param new_value: The value to be set.
246 :type new_value: str, dict or list
247 :returns: Original data with the new value.
252 for path_item in path[:-1]:
253 if isinstance(path_item, str):
255 data = data[path_item]
258 data = data[path_item]
259 elif isinstance(path_item, tuple):
263 for data_item in data[path_item[0]]:
264 if data_item[path_item[1]] == path_item[2]:
265 data = data[path_item[0]][index]
270 data[path_item[0]].append({path_item[1]: path_item[2]})
271 data = data[path_item[0]][-1]
275 if not path[-1] in data.keys():
278 if isinstance(new_value, list) and isinstance(data[path[-1]], list):
279 for value in new_value:
280 data[path[-1]].append(value)
282 data[path[-1]] = new_value
287 def get_honeycomb_data(node, url_file, path=""):
288 """Retrieve data from Honeycomb according to given URL.
290 :param node: Honeycomb node.
291 :param url_file: URL file. The argument contains only the name of file
292 without extension, not the full path.
293 :param path: Path which is added to the base path to identify the data.
297 :returns: Status code and content of response.
301 base_path = HoneycombUtil.read_path_from_url_file(url_file)
302 path = base_path + path
303 status_code, resp = HTTPRequest.get(node, path)
304 return status_code, loads(resp)
307 def put_honeycomb_data(node, url_file, data, path="",
308 data_representation=DataRepresentation.JSON):
309 """Send configuration data using PUT request and return the status code
310 and response content.
312 :param node: Honeycomb node.
313 :param url_file: URL file. The argument contains only the name of file
314 without extension, not the full path.
315 :param data: Configuration data to be sent to Honeycomb.
316 :param path: Path which is added to the base path to identify the data.
317 :param data_representation: How the data is represented.
320 :type data: dict, str
322 :type data_representation: DataRepresentation
323 :returns: Status code and content of response.
325 :raises HoneycombError: If the given data representation is not defined
330 header = HEADERS[data_representation]
331 except AttributeError as err:
332 raise HoneycombError("Wrong data representation: {0}.".
333 format(data_representation), repr(err))
334 if data_representation == DataRepresentation.JSON:
339 base_path = HoneycombUtil.read_path_from_url_file(url_file)
340 path = base_path + path
342 return HTTPRequest.put(
343 node=node, path=path, headers=header, payload=data)
346 def post_honeycomb_data(node, url_file, data=None,
347 data_representation=DataRepresentation.JSON,
349 """Send a POST request and return the status code and response content.
351 :param node: Honeycomb node.
352 :param url_file: URL file. The argument contains only the name of file
353 without extension, not the full path.
354 :param data: Configuration data to be sent to Honeycomb.
355 :param data_representation: How the data is represented.
356 :param timeout: How long to wait for the server to send data before
360 :type data: dict, str
361 :type data_representation: DataRepresentation
363 :returns: Status code and content of response.
365 :raises HoneycombError: If the given data representation is not defined
370 header = HEADERS[data_representation]
371 except AttributeError as err:
372 raise HoneycombError("Wrong data representation: {0}.".
373 format(data_representation), repr(err))
374 if data_representation == DataRepresentation.JSON:
377 path = HoneycombUtil.read_path_from_url_file(url_file)
378 return HTTPRequest.post(
379 node=node, path=path, headers=header, payload=data, timeout=timeout)
382 def delete_honeycomb_data(node, url_file, path=""):
383 """Delete data from Honeycomb according to given URL.
385 :param node: Honeycomb node.
386 :param url_file: URL file. The argument contains only the name of file
387 without extension, not the full path.
388 :param path: Path which is added to the base path to identify the data.
392 :returns: Status code and content of response.
396 base_path = HoneycombUtil.read_path_from_url_file(url_file)
397 path = base_path + path
398 return HTTPRequest.delete(node, path)