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