Blob Blame History Raw
################################################################################
# BSD LICENSE
#
# Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of Intel Corporation nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
################################################################################

"""
REST API module
Power Profiles
"""
from functools import wraps

from copy import deepcopy
from flask_restful import Resource, request
import jsonschema

from power import AdmissionControlError

from rest.rest_exceptions import NotFound, BadRequest, MethodNotAllowed
from rest.rest_auth import auth

import sstbf

import common


def _get_power_profiles_expert_mode():
    return common.CONFIG_STORE.get_global_attr('power_profiles_expert_mode', False)


def check_allowed(f):
    @wraps(f)
    def func_wrapper(*args, **kwargs):
        if not _get_power_profiles_expert_mode():
            raise MethodNotAllowed("Please enable Expert Mode first.")

        if sstbf.is_sstbf_configured():
            raise MethodNotAllowed("SST-BF configured, please un-configure SST-BF first")

        return f(*args, **kwargs)
    return func_wrapper


class Power(Resource):
    """
    Handles /power_profiles/<profile_id> HTTP requests
    """

    method_decorators = {'delete': [check_allowed], 'put' : [check_allowed]}

    @staticmethod
    @auth.login_required
    def get(profile_id):
        """
        Handles HTTP GET /power_profiles/<profile_id> request.
        Retrieve single power profile
        Raises NotFound

        Parameters:
            profile_id: Id of power profile to retrieve

        Returns:
            response, status code
        """

        data = common.CONFIG_STORE.get_config()
        if 'power_profiles' not in data:
            raise NotFound("No power profiles in config file")

        for power in data['power_profiles']:
            if not power['id'] == int(profile_id):
                continue

            return power, 200

        raise NotFound("Power profile " + str(profile_id) + " not found in config")


    @staticmethod
    @auth.login_required
    def delete(profile_id):
        """
        Handles HTTP DELETE /power_profiles/<profile_id> request.
        Deletes single Power Profile
        Raises NotFound, BadRequest

        Parameters:
            profile_id: Id of power_profile to delete

        Returns:
            response, status code
        """

        data = deepcopy(common.CONFIG_STORE.get_config())

        if 'power_profiles' not in data:
            raise NotFound("No Power Profiles in config file")

        for profile in data['power_profiles']:
            if profile['id'] != int(profile_id):
                continue

            for pool in data['pools']:
                if 'power_profile' not in pool:
                    continue

                if pool['power_profile'] == int(profile_id):
                    raise BadRequest("POWER PROFILE {} is in use.".format(str(profile_id)))

            # remove profile
            data['power_profiles'].remove(profile)
            common.CONFIG_STORE.set_config(data)

            res = {'message': "POWER PROFILE " + str(profile_id) + " deleted"}
            return res, 200

        raise NotFound("POWER PROFILE " + str(profile_id) + " not found in config")


    @staticmethod
    @auth.login_required
    def put(profile_id):
        # pylint: disable=too-many-branches
        """
        Handles HTTP PUT /power_profiles/<profile_id> request.
        Modifies a Power Profile
        Raises NotFound, BadRequest

        Parameters:
            profile_id: Id of pool

        Returns:
            response, status code
        """

        json_data = request.get_json()

        # validate app schema
        try:
            schema, resolver = common.CONFIG_STORE.load_json_schema('modify_power.json')
            jsonschema.validate(json_data, schema, resolver=resolver)
        except jsonschema.ValidationError as error:
            raise BadRequest("Request validation failed - %s" % (str(error)))

        admission_control_check = json_data.pop('verify', True)

        data = deepcopy(common.CONFIG_STORE.get_config())
        if 'power_profiles' not in data:
            raise NotFound("No Power Profiles in config file")

        for profile in data['power_profiles']:
            if profile['id'] != int(profile_id):
                continue

            # set new values
            profile.update(json_data)

            try:
                common.CONFIG_STORE.validate(data, admission_control_check)
            except Exception as ex:
                raise BadRequest("POWER PROFILE " + str(profile_id) + " not updated, " + str(ex))

            common.CONFIG_STORE.set_config(data)

            res = {'message': "POWER PROFILE " + str(profile_id) + " updated"}
            return res, 200

        raise NotFound("POWER PROFILE" + str(profile_id) + " not found in config")


class Powers(Resource):
    """
    Handles /power_profiles HTTP requests
    """

    method_decorators = {'post': [check_allowed]}

    @staticmethod
    @auth.login_required
    def get():
        """
        Handles HTTP GET /power_profiles request.
        Retrieve all power profiles
        Raises NotFound

        Returns:
            response, status code
        """
        data = common.CONFIG_STORE.get_config()
        if 'power_profiles' not in data:
            raise NotFound("No power profiles in config file")

        return data['power_profiles'], 200


    @staticmethod
    @auth.login_required
    def post():
        """
        Handles HTTP POST /power_profiles request.
        Add a new Power Profile
        Raises BadRequest

        Returns:
            response, status code
        """

        # validate power profile schema
        json_data = request.get_json()

        try:
            schema, resolver = common.CONFIG_STORE.load_json_schema('add_power.json')
            jsonschema.validate(json_data, schema, resolver=resolver)
        except jsonschema.ValidationError as error:
            raise BadRequest("Request validation failed - %s" % (str(error)))

        json_data['id'] = common.CONFIG_STORE.get_new_power_profile_id()

        data = deepcopy(common.CONFIG_STORE.get_config())
        if 'power_profiles' not in data:
            data['power_profiles'] = []
        data['power_profiles'].append(json_data)

        try:
            common.CONFIG_STORE.validate(data, False)
        except Exception as ex:
            raise BadRequest("New POWER PROFILE not added, " + str(ex))

        common.CONFIG_STORE.set_config(data)

        res = {
            'id': json_data['id'],
            'message': "New POWER PROFILE {} added".format(json_data['id'])
        }

        return res, 201