Decrease log level in Energy messages
[functest.git] / functest / energy / energy.py
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3
4 # Copyright (c) 2017 Orange and others.
5 #
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10
11 """This module manages calls to Energy recording API."""
12
13 import json
14 import logging
15 import os
16 import traceback
17
18 from functools import wraps
19 import requests
20 from six.moves import urllib
21
22
23 def finish_session(current_scenario):
24     """Finish a recording session."""
25     if current_scenario is None:
26         EnergyRecorder.stop()
27     else:
28         EnergyRecorder.logger.debug("Restoring previous scenario (%s/%s)",
29                                     current_scenario["scenario"],
30                                     current_scenario["step"])
31         EnergyRecorder.submit_scenario(
32             current_scenario["scenario"],
33             current_scenario["step"]
34         )
35
36
37 def enable_recording(method):
38     """
39     Record energy during method execution.
40
41     Decorator to record energy during "method" exection.
42
43         param method: Method to suround with start and stop
44         :type method: function
45
46         .. note:: "method" should belong to a class having a "case_name"
47                   attribute
48     """
49     @wraps(method)
50     def wrapper(*args):
51         """
52         Record energy during method execution (implementation).
53
54         Wrapper for decorator to handle method arguments.
55         """
56         current_scenario = EnergyRecorder.get_current_scenario()
57         EnergyRecorder.start(args[0].case_name)
58         try:
59             return_value = method(*args)
60             finish_session(current_scenario)
61         except Exception as exc:  # pylint: disable=broad-except
62             EnergyRecorder.logger.exception(exc)
63             finish_session(current_scenario)
64             raise exc
65         return return_value
66     return wrapper
67
68
69 # Class to manage energy recording sessions
70 class EnergyRecorder(object):
71     """Manage Energy recording session."""
72
73     logger = logging.getLogger(__name__)
74     # Energy recording API connectivity settings
75     # see load_config method
76     energy_recorder_api = None
77
78     # Default initial step
79     INITIAL_STEP = "running"
80
81     # Default connection timeout
82     CONNECTION_TIMEOUT = 4
83
84     @staticmethod
85     def load_config():
86         """
87         Load connectivity settings from yaml.
88
89         Load connectivity settings to Energy recording API
90         Use functest global config yaml file
91         (see functest_utils.get_functest_config)
92         """
93         # Singleton pattern for energy_recorder_api static member
94         # Load only if not previouly done
95         if EnergyRecorder.energy_recorder_api is None:
96             assert os.environ['NODE_NAME']
97             assert os.environ["ENERGY_RECORDER_API_URL"]
98             environment = os.environ['NODE_NAME']
99             energy_recorder_uri = os.environ["ENERGY_RECORDER_API_URL"]
100
101             # Creds
102             creds_usr = os.environ.get("ENERGY_RECORDER_API_USER", "")
103             creds_pass = os.environ.get("ENERGY_RECORDER_API_PASSWORD", "")
104
105             uri_comp = "/recorders/environment/"
106             uri_comp += urllib.parse.quote_plus(environment)
107
108             if creds_usr != "" and creds_pass != "":
109                 energy_recorder_api_auth = (creds_usr, creds_pass)
110             else:
111                 energy_recorder_api_auth = None
112
113             try:
114                 resp = requests.get(energy_recorder_uri + "/monitoring/ping",
115                                     auth=energy_recorder_api_auth,
116                                     headers={
117                                         'content-type': 'application/json'
118                                     },
119                                     timeout=EnergyRecorder.CONNECTION_TIMEOUT)
120                 api_available = json.loads(resp.text)["status"] == "OK"
121                 EnergyRecorder.logger.info(
122                     "API recorder available at : %s",
123                     energy_recorder_uri + uri_comp)
124             except Exception as exc:  # pylint: disable=broad-except
125                 EnergyRecorder.logger.info(
126                     "Energy recorder API is not available, cause=%s",
127                     str(exc))
128                 api_available = False
129             # Final config
130             EnergyRecorder.energy_recorder_api = {
131                 "uri": energy_recorder_uri + uri_comp,
132                 "auth": energy_recorder_api_auth,
133                 "available": api_available
134             }
135         return EnergyRecorder.energy_recorder_api["available"]
136
137     @staticmethod
138     def submit_scenario(scenario, step):
139         """
140         Submit a complet scenario definition to Energy recorder API.
141
142             param scenario: Scenario name
143             :type scenario: string
144             param step: Step name
145             :type step: string
146         """
147         try:
148             return_status = True
149             # Ensure that connectyvity settings are loaded
150             if EnergyRecorder.load_config():
151                 EnergyRecorder.logger.debug("Submitting scenario (%s/%s)",
152                                             scenario, step)
153
154                 # Create API payload
155                 payload = {
156                     "step": step,
157                     "scenario": scenario
158                 }
159                 # Call API to start energy recording
160                 response = requests.post(
161                     EnergyRecorder.energy_recorder_api["uri"],
162                     data=json.dumps(payload),
163                     auth=EnergyRecorder.energy_recorder_api["auth"],
164                     headers={
165                         'content-type': 'application/json'
166                     },
167                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
168                 )
169                 if response.status_code != 200:
170                     EnergyRecorder.logger.error(
171                         "Error while submitting scenario\n%s",
172                         response.text)
173                     return_status = False
174         except requests.exceptions.ConnectionError:
175             EnergyRecorder.logger.warning(
176                 "submit_scenario: Unable to connect energy recorder API")
177             return_status = False
178         except Exception:  # pylint: disable=broad-except
179             # Default exception handler to ensure that method
180             # is safe for caller
181             EnergyRecorder.logger.info(
182                 "Error while submitting scenarion to energy recorder API\n%s",
183                 traceback.format_exc()
184             )
185             return_status = False
186         return return_status
187
188     @staticmethod
189     def start(scenario):
190         """
191         Start a recording session for scenario.
192
193             param scenario: Starting scenario
194             :type scenario: string
195         """
196         return_status = True
197         try:
198             if EnergyRecorder.load_config():
199                 EnergyRecorder.logger.debug("Starting recording")
200                 return_status = EnergyRecorder.submit_scenario(
201                     scenario,
202                     EnergyRecorder.INITIAL_STEP
203                 )
204
205         except Exception:  # pylint: disable=broad-except
206             # Default exception handler to ensure that method
207             # is safe for caller
208             EnergyRecorder.logger.info(
209                 "Error while starting energy recorder API\n%s",
210                 traceback.format_exc()
211             )
212             return_status = False
213         return return_status
214
215     @staticmethod
216     def stop():
217         """Stop current recording session."""
218         return_status = True
219         try:
220             # Ensure that connectyvity settings are loaded
221             if EnergyRecorder.load_config():
222                 EnergyRecorder.logger.debug("Stopping recording")
223
224                 # Call API to stop energy recording
225                 response = requests.delete(
226                     EnergyRecorder.energy_recorder_api["uri"],
227                     auth=EnergyRecorder.energy_recorder_api["auth"],
228                     headers={
229                         'content-type': 'application/json'
230                     },
231                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
232                 )
233                 if response.status_code != 200:
234                     EnergyRecorder.logger.error(
235                         "Error while stating energy recording session\n%s",
236                         response.text)
237                     return_status = False
238         except requests.exceptions.ConnectionError:
239             EnergyRecorder.logger.warning(
240                 "stop: Unable to connect energy recorder API")
241             return_status = False
242         except Exception:  # pylint: disable=broad-except
243             # Default exception handler to ensure that method
244             # is safe for caller
245             EnergyRecorder.logger.info(
246                 "Error while stoping energy recorder API\n%s",
247                 traceback.format_exc()
248             )
249             return_status = False
250         return return_status
251
252     @staticmethod
253     def set_step(step):
254         """Notify energy recording service of current step of the testcase."""
255         return_status = True
256         try:
257             # Ensure that connectyvity settings are loaded
258             if EnergyRecorder.load_config():
259                 EnergyRecorder.logger.debug("Setting step")
260
261                 # Create API payload
262                 payload = {
263                     "step": step,
264                 }
265
266                 # Call API to define step
267                 response = requests.post(
268                     EnergyRecorder.energy_recorder_api["uri"] + "/step",
269                     data=json.dumps(payload),
270                     auth=EnergyRecorder.energy_recorder_api["auth"],
271                     headers={
272                         'content-type': 'application/json'
273                     },
274                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
275                 )
276                 if response.status_code != 200:
277                     EnergyRecorder.logger.error(
278                         "Error while setting current step of testcase\n%s",
279                         response.text)
280                     return_status = False
281         except requests.exceptions.ConnectionError:
282             EnergyRecorder.logger.warning(
283                 "set_step: Unable to connect energy recorder API")
284             return_status = False
285         except Exception:  # pylint: disable=broad-except
286             # Default exception handler to ensure that method
287             # is safe for caller
288             EnergyRecorder.logger.info(
289                 "Error while setting step on energy recorder API\n%s",
290                 traceback.format_exc()
291             )
292             return_status = False
293         return return_status
294
295     @staticmethod
296     def get_current_scenario():
297         """Get current running scenario (if any, None else)."""
298         return_value = None
299         try:
300             # Ensure that connectyvity settings are loaded
301             if EnergyRecorder.load_config():
302                 EnergyRecorder.logger.debug("Getting current scenario")
303
304                 # Call API get running scenario
305                 response = requests.get(
306                     EnergyRecorder.energy_recorder_api["uri"],
307                     auth=EnergyRecorder.energy_recorder_api["auth"],
308                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
309                 )
310                 if response.status_code == 200:
311                     return_value = json.loads(response.text)
312                 elif response.status_code == 404:
313                     EnergyRecorder.logger.info(
314                         "No current running scenario at %s",
315                         EnergyRecorder.energy_recorder_api["uri"])
316                     return_value = None
317                 else:
318                     EnergyRecorder.logger.error(
319                         "Error while getting current scenario\n%s",
320                         response.text)
321                     return_value = None
322         except requests.exceptions.ConnectionError:
323             EnergyRecorder.logger.warning(
324                 "get_currernt_sceario: Unable to connect energy recorder API")
325             return_value = None
326         except Exception:  # pylint: disable=broad-except
327             # Default exception handler to ensure that method
328             # is safe for caller
329             EnergyRecorder.logger.info(
330                 "Error while getting current scenario from energy recorder API"
331                 "\n%s", traceback.format_exc()
332             )
333             return_value = None
334         return return_value