Merge "Switch to OS_PROJECT_NAME"
[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 urllib
16
17 from functools import wraps
18 import requests
19
20 from functest.utils.constants import CONST
21 import functest.utils.functest_utils as ft_utils
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             environment = CONST.__getattribute__('NODE_NAME')
98
99             # API URL
100             energy_recorder_uri = ft_utils.get_functest_config(
101                 "energy_recorder.api_url")
102             assert energy_recorder_uri
103             assert environment
104
105             uri_comp = "/recorders/environment/"
106             uri_comp += urllib.quote_plus(environment)
107
108             # Creds
109             creds_usr = ft_utils.get_functest_config(
110                 "energy_recorder.api_user")
111             creds_pass = ft_utils.get_functest_config(
112                 "energy_recorder.api_password")
113
114             if creds_usr != "" and creds_pass != "":
115                 energy_recorder_api_auth = (creds_usr, creds_pass)
116             else:
117                 energy_recorder_api_auth = None
118
119             try:
120                 resp = requests.get(energy_recorder_uri + "/monitoring/ping",
121                                     auth=energy_recorder_api_auth,
122                                     headers={
123                                         'content-type': 'application/json'
124                                     },
125                                     timeout=EnergyRecorder.CONNECTION_TIMEOUT)
126                 api_available = json.loads(resp.text)["status"] == "OK"
127                 EnergyRecorder.logger.info(
128                     "API recorder available at : %s",
129                     energy_recorder_uri + uri_comp)
130             except Exception as exc:  # pylint: disable=broad-except
131                 EnergyRecorder.logger.info(
132                     "Energy recorder API is not available, cause=%s",
133                     exc.message)
134                 api_available = False
135             # Final config
136             EnergyRecorder.energy_recorder_api = {
137                 "uri": energy_recorder_uri + uri_comp,
138                 "auth": energy_recorder_api_auth,
139                 "available": api_available
140             }
141         return EnergyRecorder.energy_recorder_api["available"]
142
143     @staticmethod
144     def submit_scenario(scenario, step):
145         """
146         Submit a complet scenario definition to Energy recorder API.
147
148             param scenario: Scenario name
149             :type scenario: string
150             param step: Step name
151             :type step: string
152         """
153         try:
154             return_status = True
155             # Ensure that connectyvity settings are loaded
156             if EnergyRecorder.load_config():
157                 EnergyRecorder.logger.debug("Submitting scenario (%s/%s)",
158                                             scenario, step)
159
160                 # Create API payload
161                 payload = {
162                     "step": step,
163                     "scenario": scenario
164                 }
165                 # Call API to start energy recording
166                 response = requests.post(
167                     EnergyRecorder.energy_recorder_api["uri"],
168                     data=json.dumps(payload),
169                     auth=EnergyRecorder.energy_recorder_api["auth"],
170                     headers={
171                         'content-type': 'application/json'
172                     },
173                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
174                 )
175                 if response.status_code != 200:
176                     EnergyRecorder.logger.error(
177                         "Error while submitting scenario\n%s",
178                         response.text)
179                     return_status = False
180         except requests.exceptions.ConnectionError:
181             EnergyRecorder.logger.warning(
182                 "submit_scenario: Unable to connect energy recorder API")
183             return_status = False
184         except Exception:  # pylint: disable=broad-except
185             # Default exception handler to ensure that method
186             # is safe for caller
187             EnergyRecorder.logger.exception(
188                 "Error while submitting scenarion to energy recorder API"
189             )
190             return_status = False
191         return return_status
192
193     @staticmethod
194     def start(scenario):
195         """
196         Start a recording session for scenario.
197
198             param scenario: Starting scenario
199             :type scenario: string
200         """
201         return_status = True
202         try:
203             if EnergyRecorder.load_config():
204                 EnergyRecorder.logger.debug("Starting recording")
205                 return_status = EnergyRecorder.submit_scenario(
206                     scenario,
207                     EnergyRecorder.INITIAL_STEP
208                 )
209
210         except Exception:  # pylint: disable=broad-except
211             # Default exception handler to ensure that method
212             # is safe for caller
213             EnergyRecorder.logger.exception(
214                 "Error while starting energy recorder API"
215             )
216             return_status = False
217         return return_status
218
219     @staticmethod
220     def stop():
221         """Stop current recording session."""
222         return_status = True
223         try:
224             # Ensure that connectyvity settings are loaded
225             if EnergyRecorder.load_config():
226                 EnergyRecorder.logger.debug("Stopping recording")
227
228                 # Call API to stop energy recording
229                 response = requests.delete(
230                     EnergyRecorder.energy_recorder_api["uri"],
231                     auth=EnergyRecorder.energy_recorder_api["auth"],
232                     headers={
233                         'content-type': 'application/json'
234                     },
235                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
236                 )
237                 if response.status_code != 200:
238                     EnergyRecorder.logger.error(
239                         "Error while stating energy recording session\n%s",
240                         response.text)
241                     return_status = False
242         except requests.exceptions.ConnectionError:
243             EnergyRecorder.logger.warning(
244                 "stop: Unable to connect energy recorder API")
245             return_status = False
246         except Exception:  # pylint: disable=broad-except
247             # Default exception handler to ensure that method
248             # is safe for caller
249             EnergyRecorder.logger.exception(
250                 "Error while stoping energy recorder API"
251             )
252             return_status = False
253         return return_status
254
255     @staticmethod
256     def set_step(step):
257         """Notify energy recording service of current step of the testcase."""
258         return_status = True
259         try:
260             # Ensure that connectyvity settings are loaded
261             if EnergyRecorder.load_config():
262                 EnergyRecorder.logger.debug("Setting step")
263
264                 # Create API payload
265                 payload = {
266                     "step": step,
267                 }
268
269                 # Call API to define step
270                 response = requests.post(
271                     EnergyRecorder.energy_recorder_api["uri"] + "/step",
272                     data=json.dumps(payload),
273                     auth=EnergyRecorder.energy_recorder_api["auth"],
274                     headers={
275                         'content-type': 'application/json'
276                     },
277                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
278                 )
279                 if response.status_code != 200:
280                     EnergyRecorder.logger.error(
281                         "Error while setting current step of testcase\n%s",
282                         response.text)
283                     return_status = False
284         except requests.exceptions.ConnectionError:
285             EnergyRecorder.logger.warning(
286                 "set_step: Unable to connect energy recorder API")
287             return_status = False
288         except Exception:  # pylint: disable=broad-except
289             # Default exception handler to ensure that method
290             # is safe for caller
291             EnergyRecorder.logger.exception(
292                 "Error while setting step on energy recorder API"
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.exception(
332                 "Error while getting current scenario from energy recorder API"
333             )
334             return_value = None
335         return return_value