Merge "Obtain pod_name by CONST instead of get function"
[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 import urllib3
20
21 from functest.utils.constants import CONST
22 import functest.utils.functest_utils as ft_utils
23
24
25 def finish_session(current_scenario):
26     """Finish a recording session."""
27     if current_scenario is None:
28         EnergyRecorder.stop()
29     else:
30         EnergyRecorder.submit_scenario(
31             current_scenario["scenario"],
32             current_scenario["step"]
33         )
34
35
36 def enable_recording(method):
37     """
38     Record energy during method execution.
39
40     Decorator to record energy during "method" exection.
41
42         param method: Method to suround with start and stop
43         :type method: function
44
45         .. note:: "method" should belong to a class having a "case_name"
46                   attribute
47     """
48     @wraps(method)
49     def wrapper(*args):
50         """
51         Record energy during method execution (implementation).
52
53         Wrapper for decorator to handle method arguments.
54         """
55         current_scenario = EnergyRecorder.get_current_scenario()
56         EnergyRecorder.start(args[0].case_name)
57         try:
58             return_value = method(*args)
59             finish_session(current_scenario)
60         except Exception as exc:  # pylint: disable=broad-except
61             EnergyRecorder.logger.exception(exc)
62             finish_session(current_scenario)
63             raise exc
64         return return_value
65     return wrapper
66
67
68 # Class to manage energy recording sessions
69 class EnergyRecorder(object):
70     """Manage Energy recording session."""
71
72     logger = logging.getLogger(__name__)
73     # Energy recording API connectivity settings
74     # see load_config method
75     energy_recorder_api = None
76
77     # Default initial step
78     INITIAL_STEP = "running"
79
80     # Default connection timeout
81     CONNECTION_TIMOUT = urllib3.Timeout(connect=1, read=3)
82
83     @staticmethod
84     def load_config():
85         """
86         Load connectivity settings from yaml.
87
88         Load connectivity settings to Energy recording API
89         Use functest global config yaml file
90         (see functest_utils.get_functest_config)
91         """
92         # Singleton pattern for energy_recorder_api static member
93         # Load only if not previouly done
94         if EnergyRecorder.energy_recorder_api is None:
95             environment = CONST.__getattribute__('NODE_NAME')
96
97             # API URL
98             energy_recorder_uri = ft_utils.get_functest_config(
99                 "energy_recorder.api_url")
100             assert energy_recorder_uri
101             assert environment
102
103             uri_comp = "/recorders/environment/"
104             uri_comp += urllib.quote_plus(environment)
105             EnergyRecorder.logger.debug(
106                 "API recorder at: " + energy_recorder_uri + uri_comp)
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_TIMOUT)
126                 api_available = json.loads(resp.text)["status"] == "OK"
127             except Exception:  # pylint: disable=broad-except
128                 EnergyRecorder.logger.error(
129                     "Energy recorder API is not available")
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")
154
155                 # Create API payload
156                 payload = {
157                     "step": step,
158                     "scenario": scenario
159                 }
160                 # Call API to start energy recording
161                 response = requests.post(
162                     EnergyRecorder.energy_recorder_api["uri"],
163                     data=json.dumps(payload),
164                     auth=EnergyRecorder.energy_recorder_api["auth"],
165                     headers={
166                         'content-type': 'application/json'
167                     },
168                     timeout=EnergyRecorder.CONNECTION_TIMOUT
169                 )
170                 if response.status_code != 200:
171                     EnergyRecorder.logger.error(
172                         "Error while submitting scenario\n%s",
173                         response.text)
174                     return_status = False
175         except requests.exceptions.ConnectionError:
176             EnergyRecorder.logger.warning(
177                 "submit_scenario: Unable to connect energy recorder API")
178             return_status = False
179         except Exception:  # pylint: disable=broad-except
180             # Default exception handler to ensure that method
181             # is safe for caller
182             EnergyRecorder.logger.exception(
183                 "Error while submitting scenarion to energy recorder API"
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.exception(
209                 "Error while starting energy recorder API"
210             )
211             return_status = False
212         return return_status
213
214     @staticmethod
215     def stop():
216         """Stop current recording session."""
217         return_status = True
218         try:
219             # Ensure that connectyvity settings are loaded
220             if EnergyRecorder.load_config():
221                 EnergyRecorder.logger.debug("Stopping recording")
222
223                 # Call API to stop energy recording
224                 response = requests.delete(
225                     EnergyRecorder.energy_recorder_api["uri"],
226                     auth=EnergyRecorder.energy_recorder_api["auth"],
227                     headers={
228                         'content-type': 'application/json'
229                     },
230                     timeout=EnergyRecorder.CONNECTION_TIMOUT
231                 )
232                 if response.status_code != 200:
233                     EnergyRecorder.logger.error(
234                         "Error while stating energy recording session\n%s",
235                         response.text)
236                     return_status = False
237         except requests.exceptions.ConnectionError:
238             EnergyRecorder.logger.warning(
239                 "stop: Unable to connect energy recorder API")
240             return_status = False
241         except Exception:  # pylint: disable=broad-except
242             # Default exception handler to ensure that method
243             # is safe for caller
244             EnergyRecorder.logger.exception(
245                 "Error while stoping energy recorder API"
246             )
247             return_status = False
248         return return_status
249
250     @staticmethod
251     def set_step(step):
252         """Notify energy recording service of current step of the testcase."""
253         return_status = True
254         try:
255             # Ensure that connectyvity settings are loaded
256             if EnergyRecorder.load_config():
257                 EnergyRecorder.logger.debug("Setting step")
258
259                 # Create API payload
260                 payload = {
261                     "step": step,
262                 }
263
264                 # Call API to define step
265                 response = requests.post(
266                     EnergyRecorder.energy_recorder_api["uri"] + "/step",
267                     data=json.dumps(payload),
268                     auth=EnergyRecorder.energy_recorder_api["auth"],
269                     headers={
270                         'content-type': 'application/json'
271                     },
272                     timeout=EnergyRecorder.CONNECTION_TIMOUT
273                 )
274                 if response.status_code != 200:
275                     EnergyRecorder.logger.error(
276                         "Error while setting current step of testcase\n%s",
277                         response.text)
278                     return_status = False
279         except requests.exceptions.ConnectionError:
280             EnergyRecorder.logger.warning(
281                 "set_step: Unable to connect energy recorder API")
282             return_status = False
283         except Exception:  # pylint: disable=broad-except
284             # Default exception handler to ensure that method
285             # is safe for caller
286             EnergyRecorder.logger.exception(
287                 "Error while setting step on energy recorder API"
288             )
289             return_status = False
290         return return_status
291
292     @staticmethod
293     def get_current_scenario():
294         """Get current running scenario (if any, None else)."""
295         return_value = None
296         try:
297             # Ensure that connectyvity settings are loaded
298             if EnergyRecorder.load_config():
299                 EnergyRecorder.logger.debug("Getting current scenario")
300
301                 # Call API get running scenario
302                 response = requests.get(
303                     EnergyRecorder.energy_recorder_api["uri"],
304                     auth=EnergyRecorder.energy_recorder_api["auth"],
305                     timeout=EnergyRecorder.CONNECTION_TIMOUT
306                 )
307                 if response.status_code == 200:
308                     return_value = json.loads(response.text)
309                 elif response.status_code == 404:
310                     EnergyRecorder.logger.info(
311                         "No current running scenario at %s",
312                         EnergyRecorder.energy_recorder_api["uri"])
313                     return_value = None
314                 else:
315                     EnergyRecorder.logger.error(
316                         "Error while getting current scenario\n%s",
317                         response.text)
318                     return_value = None
319         except requests.exceptions.ConnectionError:
320             EnergyRecorder.logger.warning(
321                 "get_currernt_sceario: Unable to connect energy recorder API")
322             return_value = None
323         except Exception:  # pylint: disable=broad-except
324             # Default exception handler to ensure that method
325             # is safe for caller
326             EnergyRecorder.logger.exception(
327                 "Error while getting current scenario from energy recorder API"
328             )
329             return_value = None
330         return return_value