Merge "Change calls to SNAPS library exposing OpenStack instance objects."
[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 import functest.utils.functest_utils as ft_utils
21
22
23 def finish_session(current_scenario):
24     """Finish a recording session."""
25     if current_scenario is None:
26         EnergyRecorder.stop()
27     else:
28         EnergyRecorder.submit_scenario(
29             current_scenario["scenario"],
30             current_scenario["step"]
31         )
32
33
34 def enable_recording(method):
35     """
36     Record energy during method execution.
37
38     Decorator to record energy during "method" exection.
39
40         param method: Method to suround with start and stop
41         :type method: function
42
43         .. note:: "method" should belong to a class having a "case_name"
44                   attribute
45     """
46     @wraps(method)
47     def wrapper(*args):
48         """
49         Record energy during method execution (implementation).
50
51         Wrapper for decorator to handle method arguments.
52         """
53         current_scenario = EnergyRecorder.get_current_scenario()
54         EnergyRecorder.start(args[0].case_name)
55         try:
56             return_value = method(*args)
57             finish_session(current_scenario)
58         except Exception:  # pylint: disable=broad-except
59             finish_session(current_scenario)
60             raise
61         return return_value
62     return wrapper
63
64
65 # Class to manage energy recording sessions
66 class EnergyRecorder(object):
67     """Manage Energy recording session."""
68
69     logger = logging.getLogger(__name__)
70     # Energy recording API connectivity settings
71     # see load_config method
72     energy_recorder_api = None
73
74     # Default initial step
75     INITIAL_STEP = "running"
76
77     @staticmethod
78     def load_config():
79         """
80         Load connectivity settings from yaml.
81
82         Load connectivity settings to Energy recording API
83         Use functest global config yaml file
84         (see functest_utils.get_functest_config)
85         """
86         # Singleton pattern for energy_recorder_api static member
87         # Load only if not previouly done
88         if EnergyRecorder.energy_recorder_api is None:
89             environment = ft_utils.get_pod_name()
90
91             # API URL
92             energy_recorder_uri = ft_utils.get_functest_config(
93                 "energy_recorder.api_url")
94             assert energy_recorder_uri
95             assert environment
96
97             energy_recorder_uri += "/recorders/environment/"
98             energy_recorder_uri += urllib.quote_plus(environment)
99             EnergyRecorder.logger.debug(
100                 "API recorder at: " + energy_recorder_uri)
101
102             # Creds
103             user = ft_utils.get_functest_config(
104                 "energy_recorder.api_user")
105             password = ft_utils.get_functest_config(
106                 "energy_recorder.api_password")
107
108             if user != "" and password != "":
109                 energy_recorder_api_auth = (user, password)
110             else:
111                 energy_recorder_api_auth = None
112
113             # Final config
114             EnergyRecorder.energy_recorder_api = {
115                 "uri": energy_recorder_uri,
116                 "auth": energy_recorder_api_auth
117             }
118
119     @staticmethod
120     def submit_scenario(scenario, step):
121         """
122         Submit a complet scenario definition to Energy recorder API.
123
124             param scenario: Scenario name
125             :type scenario: string
126             param step: Step name
127             :type step: string
128         """
129         return_status = True
130         try:
131             EnergyRecorder.logger.debug("Submitting scenario")
132             # Ensure that connectyvity settings are loaded
133             EnergyRecorder.load_config()
134
135             # Create API payload
136             payload = {
137                 "step": step,
138                 "scenario": scenario
139             }
140             # Call API to start energy recording
141             response = requests.post(
142                 EnergyRecorder.energy_recorder_api["uri"],
143                 data=json.dumps(payload),
144                 auth=EnergyRecorder.energy_recorder_api["auth"],
145                 headers={
146                     'content-type': 'application/json'
147                 }
148             )
149             if response.status_code != 200:
150                 log_msg = "Error while submitting scenario\n{}"
151                 log_msg = log_msg.format(response.text)
152                 EnergyRecorder.logger.info(log_msg)
153                 return_status = False
154         except Exception:  # pylint: disable=broad-except
155             # Default exception handler to ensure that method
156             # is safe for caller
157             EnergyRecorder.logger.exception(
158                 "Error while submitting scenarion to energy recorder API"
159             )
160             return_status = False
161         return return_status
162
163     @staticmethod
164     def start(scenario):
165         """
166         Start a recording session for scenario.
167
168             param scenario: Starting scenario
169             :type scenario: string
170         """
171         return_status = True
172         try:
173             EnergyRecorder.logger.debug("Starting recording")
174             return_status = EnergyRecorder.submit_scenario(
175                 scenario,
176                 EnergyRecorder.INITIAL_STEP
177             )
178
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 starting energy recorder API"
184             )
185             return_status = False
186         return return_status
187
188     @staticmethod
189     def stop():
190         """Stop current recording session."""
191         EnergyRecorder.logger.debug("Stopping recording")
192         return_status = True
193         try:
194             # Ensure that connectyvity settings are loaded
195             EnergyRecorder.load_config()
196
197             # Call API to stop energy recording
198             response = requests.delete(
199                 EnergyRecorder.energy_recorder_api["uri"],
200                 auth=EnergyRecorder.energy_recorder_api["auth"],
201                 headers={
202                     'content-type': 'application/json'
203                 }
204             )
205             if response.status_code != 200:
206                 log_msg = "Error while stating energy recording session\n{}"
207                 log_msg = log_msg.format(response.text)
208                 EnergyRecorder.logger.error(log_msg)
209                 return_status = False
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 stoping energy recorder API"
215             )
216             return_status = False
217         return return_status
218
219     @staticmethod
220     def set_step(step):
221         """Notify energy recording service of current step of the testcase."""
222         EnergyRecorder.logger.debug("Setting step")
223         return_status = True
224         try:
225             # Ensure that connectyvity settings are loaded
226             EnergyRecorder.load_config()
227
228             # Create API payload
229             payload = {
230                 "step": step,
231             }
232
233             # Call API to define step
234             response = requests.post(
235                 EnergyRecorder.energy_recorder_api["uri"] + "/step",
236                 data=json.dumps(payload),
237                 auth=EnergyRecorder.energy_recorder_api["auth"],
238                 headers={
239                     'content-type': 'application/json'
240                 }
241             )
242             if response.status_code != 200:
243                 log_msg = "Error while setting current step of testcase\n{}"
244                 log_msg = log_msg.format(response.text)
245                 EnergyRecorder.logger.error(log_msg)
246                 return_status = False
247         except Exception:  # pylint: disable=broad-except
248             # Default exception handler to ensure that method
249             # is safe for caller
250             EnergyRecorder.logger.exception(
251                 "Error while setting step on energy recorder API"
252             )
253             return_status = False
254         return return_status
255
256     @staticmethod
257     def get_current_scenario():
258         """Get current running scenario (if any, None else)."""
259         EnergyRecorder.logger.debug("Getting current scenario")
260         return_value = None
261         try:
262             # Ensure that connectyvity settings are loaded
263             EnergyRecorder.load_config()
264
265             # Call API get running scenario
266             response = requests.get(
267                 EnergyRecorder.energy_recorder_api["uri"],
268                 auth=EnergyRecorder.energy_recorder_api["auth"]
269             )
270             if response.status_code == 200:
271                 return_value = json.loads(response.text)
272             elif response.status_code == 404:
273                 log_msg = "No current running scenario at {}"
274                 log_msg = log_msg.format(
275                     EnergyRecorder.energy_recorder_api["uri"])
276                 EnergyRecorder.logger.error(log_msg)
277                 return_value = None
278             else:
279                 log_msg = "Error while getting current scenario\n{}"
280                 log_msg = log_msg.format(response.text)
281                 EnergyRecorder.logger.error(log_msg)
282                 return_value = None
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 getting current scenario from energy recorder API"
288             )
289             return_value = None
290         return return_value