Get properly env vars or their default values
[functest.git] / functest / energy / energy.py
index a20c799..d5f6871 100644 (file)
 
 import json
 import logging
-import urllib
+import traceback
+
+from functools import wraps
 import requests
+from six.moves import urllib
+
+from functest.utils import env
 
-import functest.utils.functest_utils as ft_utils
+
+def finish_session(current_scenario):
+    """Finish a recording session."""
+    if current_scenario is None:
+        EnergyRecorder.stop()
+    else:
+        EnergyRecorder.logger.debug("Restoring previous scenario (%s/%s)",
+                                    current_scenario["scenario"],
+                                    current_scenario["step"])
+        EnergyRecorder.submit_scenario(
+            current_scenario["scenario"],
+            current_scenario["step"]
+        )
 
 
 def enable_recording(method):
     """
+    Record energy during method execution.
+
     Decorator to record energy during "method" exection.
 
         param method: Method to suround with start and stop
@@ -28,11 +47,22 @@ def enable_recording(method):
         .. note:: "method" should belong to a class having a "case_name"
                   attribute
     """
+    @wraps(method)
     def wrapper(*args):
-        """Wrapper for decorator to handle method arguments."""
+        """
+        Record energy during method execution (implementation).
+
+        Wrapper for decorator to handle method arguments.
+        """
+        current_scenario = EnergyRecorder.get_current_scenario()
         EnergyRecorder.start(args[0].case_name)
-        return_value = method(*args)
-        EnergyRecorder.stop()
+        try:
+            return_value = method(*args)
+            finish_session(current_scenario)
+        except Exception as exc:  # pylint: disable=broad-except
+            EnergyRecorder.logger.exception(exc)
+            finish_session(current_scenario)
+            raise exc
         return return_value
     return wrapper
 
@@ -47,7 +77,10 @@ class EnergyRecorder(object):
     energy_recorder_api = None
 
     # Default initial step
-    INITIAL_STEP = "starting"
+    INITIAL_STEP = "running"
+
+    # Default connection timeout
+    CONNECTION_TIMEOUT = 4
 
     @staticmethod
     def load_config():
@@ -61,75 +94,122 @@ class EnergyRecorder(object):
         # Singleton pattern for energy_recorder_api static member
         # Load only if not previouly done
         if EnergyRecorder.energy_recorder_api is None:
-            environment = ft_utils.get_pod_name()
-
-            # API URL
-            energy_recorder_uri = ft_utils.get_functest_config(
-                "energy_recorder.api_url")
-            assert energy_recorder_uri
-            assert environment
-
-            energy_recorder_uri += "/recorders/environment/"
-            energy_recorder_uri += urllib.quote_plus(environment)
-            EnergyRecorder.logger.debug(
-                "API recorder at: " + energy_recorder_uri)
+            assert env.get('NODE_NAME')
+            assert env.get('ENERGY_RECORDER_API_URL')
+            environment = env.get('NODE_NAME')
+            energy_recorder_uri = env.get(
+                'ENERGY_RECORDER_API_URL')
 
             # Creds
-            user = ft_utils.get_functest_config(
-                "energy_recorder.api_user")
-            password = ft_utils.get_functest_config(
-                "energy_recorder.api_password")
+            creds_usr = env.get("ENERGY_RECORDER_API_USER")
+            creds_pass = env.get("ENERGY_RECORDER_API_PASSWORD")
+
+            uri_comp = "/recorders/environment/"
+            uri_comp += urllib.parse.quote_plus(environment)
 
-            if user != "" and password != "":
-                energy_recorder_api_auth = (user, password)
+            if creds_usr != "" and creds_pass != "":
+                energy_recorder_api_auth = (creds_usr, creds_pass)
             else:
                 energy_recorder_api_auth = None
 
+            try:
+                resp = requests.get(energy_recorder_uri + "/monitoring/ping",
+                                    auth=energy_recorder_api_auth,
+                                    headers={
+                                        'content-type': 'application/json'
+                                    },
+                                    timeout=EnergyRecorder.CONNECTION_TIMEOUT)
+                api_available = json.loads(resp.text)["status"] == "OK"
+                EnergyRecorder.logger.info(
+                    "API recorder available at : %s",
+                    energy_recorder_uri + uri_comp)
+            except Exception as exc:  # pylint: disable=broad-except
+                EnergyRecorder.logger.info(
+                    "Energy recorder API is not available, cause=%s",
+                    str(exc))
+                api_available = False
             # Final config
             EnergyRecorder.energy_recorder_api = {
-                "uri": energy_recorder_uri,
-                "auth": energy_recorder_api_auth
+                "uri": energy_recorder_uri + uri_comp,
+                "auth": energy_recorder_api_auth,
+                "available": api_available
             }
+        return EnergyRecorder.energy_recorder_api["available"]
 
     @staticmethod
-    def start(scenario):
+    def submit_scenario(scenario, step):
         """
-        Start a recording session for scenario.
+        Submit a complet scenario definition to Energy recorder API.
 
-            param scenario: Starting scenario
+            param scenario: Scenario name
             :type scenario: string
+            param step: Step name
+            :type step: string
         """
-        return_status = True
         try:
-            EnergyRecorder.logger.debug("Starting recording")
+            return_status = True
             # Ensure that connectyvity settings are loaded
-            EnergyRecorder.load_config()
+            if EnergyRecorder.load_config():
+                EnergyRecorder.logger.debug("Submitting scenario (%s/%s)",
+                                            scenario, step)
 
-            # Create API payload
-            payload = {
-                "step": EnergyRecorder.INITIAL_STEP,
-                "scenario": scenario
-            }
-            # Call API to start energy recording
-            response = requests.post(
-                EnergyRecorder.energy_recorder_api["uri"],
-                data=json.dumps(payload),
-                auth=EnergyRecorder.energy_recorder_api["auth"],
-                headers={
-                    'content-type': 'application/json'
+                # Create API payload
+                payload = {
+                    "step": step,
+                    "scenario": scenario
                 }
+                # Call API to start energy recording
+                response = requests.post(
+                    EnergyRecorder.energy_recorder_api["uri"],
+                    data=json.dumps(payload),
+                    auth=EnergyRecorder.energy_recorder_api["auth"],
+                    headers={
+                        'content-type': 'application/json'
+                    },
+                    timeout=EnergyRecorder.CONNECTION_TIMEOUT
+                )
+                if response.status_code != 200:
+                    EnergyRecorder.logger.error(
+                        "Error while submitting scenario\n%s",
+                        response.text)
+                    return_status = False
+        except requests.exceptions.ConnectionError:
+            EnergyRecorder.logger.warning(
+                "submit_scenario: Unable to connect energy recorder API")
+            return_status = False
+        except Exception:  # pylint: disable=broad-except
+            # Default exception handler to ensure that method
+            # is safe for caller
+            EnergyRecorder.logger.info(
+                "Error while submitting scenarion to energy recorder API\n%s",
+                traceback.format_exc()
             )
-            if response.status_code != 200:
-                log_msg = "Error while starting energy recording session\n{}"
-                log_msg = log_msg.format(response.text)
-                EnergyRecorder.logger.info(log_msg)
-                return_status = False
+            return_status = False
+        return return_status
+
+    @staticmethod
+    def start(scenario):
+        """
+        Start a recording session for scenario.
+
+            param scenario: Starting scenario
+            :type scenario: string
+        """
+        return_status = True
+        try:
+            if EnergyRecorder.load_config():
+                EnergyRecorder.logger.debug("Starting recording")
+                return_status = EnergyRecorder.submit_scenario(
+                    scenario,
+                    EnergyRecorder.INITIAL_STEP
+                )
 
         except Exception:  # pylint: disable=broad-except
             # Default exception handler to ensure that method
             # is safe for caller
-            EnergyRecorder.logger.exception(
-                "Error while starting energy recorder API"
+            EnergyRecorder.logger.info(
+                "Error while starting energy recorder API\n%s",
+                traceback.format_exc()
             )
             return_status = False
         return return_status
@@ -137,30 +217,36 @@ class EnergyRecorder(object):
     @staticmethod
     def stop():
         """Stop current recording session."""
-        EnergyRecorder.logger.debug("Stopping recording")
         return_status = True
         try:
             # Ensure that connectyvity settings are loaded
-            EnergyRecorder.load_config()
-
-            # Call API to stop energy recording
-            response = requests.delete(
-                EnergyRecorder.energy_recorder_api["uri"],
-                auth=EnergyRecorder.energy_recorder_api["auth"],
-                headers={
-                    'content-type': 'application/json'
-                }
-            )
-            if response.status_code != 200:
-                log_msg = "Error while stating energy recording session\n{}"
-                log_msg = log_msg.format(response.text)
-                EnergyRecorder.logger.error(log_msg)
-                return_status = False
+            if EnergyRecorder.load_config():
+                EnergyRecorder.logger.debug("Stopping recording")
+
+                # Call API to stop energy recording
+                response = requests.delete(
+                    EnergyRecorder.energy_recorder_api["uri"],
+                    auth=EnergyRecorder.energy_recorder_api["auth"],
+                    headers={
+                        'content-type': 'application/json'
+                    },
+                    timeout=EnergyRecorder.CONNECTION_TIMEOUT
+                )
+                if response.status_code != 200:
+                    EnergyRecorder.logger.error(
+                        "Error while stating energy recording session\n%s",
+                        response.text)
+                    return_status = False
+        except requests.exceptions.ConnectionError:
+            EnergyRecorder.logger.warning(
+                "stop: Unable to connect energy recorder API")
+            return_status = False
         except Exception:  # pylint: disable=broad-except
             # Default exception handler to ensure that method
             # is safe for caller
-            EnergyRecorder.logger.exception(
-                "Error while stoping energy recorder API"
+            EnergyRecorder.logger.info(
+                "Error while stoping energy recorder API\n%s",
+                traceback.format_exc()
             )
             return_status = False
         return return_status
@@ -168,36 +254,83 @@ class EnergyRecorder(object):
     @staticmethod
     def set_step(step):
         """Notify energy recording service of current step of the testcase."""
-        EnergyRecorder.logger.debug("Setting step")
         return_status = True
         try:
             # Ensure that connectyvity settings are loaded
-            EnergyRecorder.load_config()
-
-            # Create API payload
-            payload = {
-                "step": step,
-            }
+            if EnergyRecorder.load_config():
+                EnergyRecorder.logger.debug("Setting step")
 
-            # Call API to define step
-            response = requests.post(
-                EnergyRecorder.energy_recorder_api["uri"] + "/step",
-                data=json.dumps(payload),
-                auth=EnergyRecorder.energy_recorder_api["auth"],
-                headers={
-                    'content-type': 'application/json'
+                # Create API payload
+                payload = {
+                    "step": step,
                 }
-            )
-            if response.status_code != 200:
-                log_msg = "Error while setting current step of testcase\n{}"
-                log_msg = log_msg.format(response.text)
-                EnergyRecorder.logger.error(log_msg)
-                return_status = False
+
+                # Call API to define step
+                response = requests.post(
+                    EnergyRecorder.energy_recorder_api["uri"] + "/step",
+                    data=json.dumps(payload),
+                    auth=EnergyRecorder.energy_recorder_api["auth"],
+                    headers={
+                        'content-type': 'application/json'
+                    },
+                    timeout=EnergyRecorder.CONNECTION_TIMEOUT
+                )
+                if response.status_code != 200:
+                    EnergyRecorder.logger.error(
+                        "Error while setting current step of testcase\n%s",
+                        response.text)
+                    return_status = False
+        except requests.exceptions.ConnectionError:
+            EnergyRecorder.logger.warning(
+                "set_step: Unable to connect energy recorder API")
+            return_status = False
         except Exception:  # pylint: disable=broad-except
             # Default exception handler to ensure that method
             # is safe for caller
-            EnergyRecorder.logger.exception(
-                "Error while setting step on energy recorder API"
+            EnergyRecorder.logger.info(
+                "Error while setting step on energy recorder API\n%s",
+                traceback.format_exc()
             )
             return_status = False
         return return_status
+
+    @staticmethod
+    def get_current_scenario():
+        """Get current running scenario (if any, None else)."""
+        return_value = None
+        try:
+            # Ensure that connectyvity settings are loaded
+            if EnergyRecorder.load_config():
+                EnergyRecorder.logger.debug("Getting current scenario")
+
+                # Call API get running scenario
+                response = requests.get(
+                    EnergyRecorder.energy_recorder_api["uri"],
+                    auth=EnergyRecorder.energy_recorder_api["auth"],
+                    timeout=EnergyRecorder.CONNECTION_TIMEOUT
+                )
+                if response.status_code == 200:
+                    return_value = json.loads(response.text)
+                elif response.status_code == 404:
+                    EnergyRecorder.logger.info(
+                        "No current running scenario at %s",
+                        EnergyRecorder.energy_recorder_api["uri"])
+                    return_value = None
+                else:
+                    EnergyRecorder.logger.error(
+                        "Error while getting current scenario\n%s",
+                        response.text)
+                    return_value = None
+        except requests.exceptions.ConnectionError:
+            EnergyRecorder.logger.warning(
+                "get_currernt_sceario: Unable to connect energy recorder API")
+            return_value = None
+        except Exception:  # pylint: disable=broad-except
+            # Default exception handler to ensure that method
+            # is safe for caller
+            EnergyRecorder.logger.info(
+                "Error while getting current scenario from energy recorder API"
+                "\n%s", traceback.format_exc()
+            )
+            return_value = None
+        return return_value