Add log details on SDK first connection.
[functest-xtesting.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_TIMEOUT = 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_TIMEOUT)
126                 api_available = json.loads(resp.text)["status"] == "OK"
127             except Exception as exc:  # pylint: disable=broad-except
128                 EnergyRecorder.logger.info(
129                     "Energy recorder API is not available, cause=%s",
130                     exc.message)
131                 api_available = False
132             # Final config
133             EnergyRecorder.energy_recorder_api = {
134                 "uri": energy_recorder_uri + uri_comp,
135                 "auth": energy_recorder_api_auth,
136                 "available": api_available
137             }
138         return EnergyRecorder.energy_recorder_api["available"]
139
140     @staticmethod
141     def submit_scenario(scenario, step):
142         """
143         Submit a complet scenario definition to Energy recorder API.
144
145             param scenario: Scenario name
146             :type scenario: string
147             param step: Step name
148             :type step: string
149         """
150         try:
151             return_status = True
152             # Ensure that connectyvity settings are loaded
153             if EnergyRecorder.load_config():
154                 EnergyRecorder.logger.debug("Submitting scenario")
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.exception(
184                 "Error while submitting scenarion to energy recorder API"
185             )
186             return_status = False
187         return return_status
188
189     @staticmethod
190     def start(scenario):
191         """
192         Start a recording session for scenario.
193
194             param scenario: Starting scenario
195             :type scenario: string
196         """
197         return_status = True
198         try:
199             if EnergyRecorder.load_config():
200                 EnergyRecorder.logger.debug("Starting recording")
201                 return_status = EnergyRecorder.submit_scenario(
202                     scenario,
203                     EnergyRecorder.INITIAL_STEP
204                 )
205
206         except Exception:  # pylint: disable=broad-except
207             # Default exception handler to ensure that method
208             # is safe for caller
209             EnergyRecorder.logger.exception(
210                 "Error while starting energy recorder API"
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.exception(
246                 "Error while stoping energy recorder API"
247             )
248             return_status = False
249         return return_status
250
251     @staticmethod
252     def set_step(step):
253         """Notify energy recording service of current step of the testcase."""
254         return_status = True
255         try:
256             # Ensure that connectyvity settings are loaded
257             if EnergyRecorder.load_config():
258                 EnergyRecorder.logger.debug("Setting step")
259
260                 # Create API payload
261                 payload = {
262                     "step": step,
263                 }
264
265                 # Call API to define step
266                 response = requests.post(
267                     EnergyRecorder.energy_recorder_api["uri"] + "/step",
268                     data=json.dumps(payload),
269                     auth=EnergyRecorder.energy_recorder_api["auth"],
270                     headers={
271                         'content-type': 'application/json'
272                     },
273                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
274                 )
275                 if response.status_code != 200:
276                     EnergyRecorder.logger.error(
277                         "Error while setting current step of testcase\n%s",
278                         response.text)
279                     return_status = False
280         except requests.exceptions.ConnectionError:
281             EnergyRecorder.logger.warning(
282                 "set_step: Unable to connect energy recorder API")
283             return_status = False
284         except Exception:  # pylint: disable=broad-except
285             # Default exception handler to ensure that method
286             # is safe for caller
287             EnergyRecorder.logger.exception(
288                 "Error while setting step on energy recorder API"
289             )
290             return_status = False
291         return return_status
292
293     @staticmethod
294     def get_current_scenario():
295         """Get current running scenario (if any, None else)."""
296         return_value = None
297         try:
298             # Ensure that connectyvity settings are loaded
299             if EnergyRecorder.load_config():
300                 EnergyRecorder.logger.debug("Getting current scenario")
301
302                 # Call API get running scenario
303                 response = requests.get(
304                     EnergyRecorder.energy_recorder_api["uri"],
305                     auth=EnergyRecorder.energy_recorder_api["auth"],
306                     timeout=EnergyRecorder.CONNECTION_TIMEOUT
307                 )
308                 if response.status_code == 200:
309                     return_value = json.loads(response.text)
310                 elif response.status_code == 404:
311                     EnergyRecorder.logger.info(
312                         "No current running scenario at %s",
313                         EnergyRecorder.energy_recorder_api["uri"])
314                     return_value = None
315                 else:
316                     EnergyRecorder.logger.error(
317                         "Error while getting current scenario\n%s",
318                         response.text)
319                     return_value = None
320         except requests.exceptions.ConnectionError:
321             EnergyRecorder.logger.warning(
322                 "get_currernt_sceario: Unable to connect energy recorder API")
323             return_value = None
324         except Exception:  # pylint: disable=broad-except
325             # Default exception handler to ensure that method
326             # is safe for caller
327             EnergyRecorder.logger.exception(
328                 "Error while getting current scenario from energy recorder API"
329             )
330             return_value = None
331         return return_value