#!/usr/bin/env python # -*- coding: UTF-8 -*- # Copyright (c) 2017 Orange and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 """This module manages calls to Energy recording API.""" import json import logging import urllib from functools import wraps import requests from functest.utils.constants import CONST 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 :type method: function .. note:: "method" should belong to a class having a "case_name" attribute """ @wraps(method) def wrapper(*args): """ 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) 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 # Class to manage energy recording sessions class EnergyRecorder(object): """Manage Energy recording session.""" logger = logging.getLogger(__name__) # Energy recording API connectivity settings # see load_config method energy_recorder_api = None # Default initial step INITIAL_STEP = "running" # Default connection timeout CONNECTION_TIMEOUT = 4 @staticmethod def load_config(): """ Load connectivity settings from yaml. Load connectivity settings to Energy recording API Use functest global config yaml file (see functest_utils.get_functest_config) """ # Singleton pattern for energy_recorder_api static member # Load only if not previouly done if EnergyRecorder.energy_recorder_api is None: environment = CONST.__getattribute__('NODE_NAME') # API URL energy_recorder_uri = ft_utils.get_functest_config( "energy_recorder.api_url") assert energy_recorder_uri assert environment uri_comp = "/recorders/environment/" uri_comp += urllib.quote_plus(environment) # Creds creds_usr = ft_utils.get_functest_config( "energy_recorder.api_user") creds_pass = ft_utils.get_functest_config( "energy_recorder.api_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", exc.message) api_available = False # Final config EnergyRecorder.energy_recorder_api = { "uri": energy_recorder_uri + uri_comp, "auth": energy_recorder_api_auth, "available": api_available } return EnergyRecorder.energy_recorder_api["available"] @staticmethod def submit_scenario(scenario, step): """ Submit a complet scenario definition to Energy recorder API. param scenario: Scenario name :type scenario: string param step: Step name :type step: string """ try: return_status = True # Ensure that connectyvity settings are loaded if EnergyRecorder.load_config(): EnergyRecorder.logger.debug("Submitting scenario (%s/%s)", scenario, step) # 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.exception( "Error while submitting scenarion to energy recorder API" ) 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" ) return_status = False return return_status @staticmethod def stop(): """Stop current recording session.""" return_status = True try: # Ensure that connectyvity settings are loaded 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" ) return_status = False return return_status @staticmethod def set_step(step): """Notify energy recording service of current step of the testcase.""" return_status = True try: # Ensure that connectyvity settings are loaded if EnergyRecorder.load_config(): EnergyRecorder.logger.debug("Setting step") # Create API payload payload = { "step": 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' }, 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" ) 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.exception( "Error while getting current scenario from energy recorder API" ) return_value = None return return_value