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