Module src.ReBalancer.ApplicationReBalancer

Expand source code
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Universidade da Coruña
# Authors:
#     - Jonatan Enes [main](jonatan.enes@udc.es)
#     - Roberto R. Expósito
#     - Juan Touriño
#
# This file is part of the ServerlessContainers framework, from
# now on referred to as ServerlessContainers.
#
# ServerlessContainers is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# ServerlessContainers is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ServerlessContainers. If not, see <http://www.gnu.org/licenses/>.

import requests

from src.MyUtils import MyUtils
from src.ReBalancer.Utils import CONFIG_DEFAULT_VALUES, app_can_be_rebalanced, get_user_apps
from src.StateDatabase import couchdb


class ApplicationRebalancer:
    def __init__(self):
        self.__couchdb_handler = couchdb.CouchDBServer()
        self.__debug = True
        self.__config = {}


    def __app_energy_can_be_rebalanced(self, application):
        return app_can_be_rebalanced(application, "application", self.__couchdb_handler)

    def __static_app_rebalancing(self, applications, max_energy):
        # Add up all the shares
        total_shares = sum([app["resources"]["energy"]["shares"] for app in applications])

        # For each application calculate the energy according to its shares
        for app in applications:
            percentage = app["resources"]["energy"]["shares"] / total_shares

            limit_energy = int(max_energy * percentage)
            current_energy = app["resources"]["energy"]["max"]

            # If the database record and the new limit are not the same, update
            if current_energy != limit_energy:
                app["resources"]["energy"]["max"] = limit_energy
                self.__couchdb_handler.update_structure(app)
                MyUtils.log_info("Updated energy limit of app {0}".format(app["name"]), self.__debug)

            # MyUtils.log_info("Processed app {0} with an energy share of {1}% and {2} Watts".format(
            #     app["name"], str("%.2f" % (100 * percentage)), limit_energy), self.__debug)

    def __dynamic_app_rebalancing(self, applications):
        donors, receivers = list(), list()
        for app in applications:
            if self.__app_energy_can_be_rebalanced(app):
                diff = app["resources"]["energy"]["max"] - app["resources"]["energy"]["usage"]
                if diff > self.__ENERGY_DIFF_PERCENTAGE * app["resources"]["energy"]["max"]:
                    donors.append(app)
                else:
                    receivers.append(app)

        if not receivers:
            MyUtils.log_info("No app to give energy shares", self.__debug)
            return
        else:
            # Order the apps from lower to upper energy limit
            apps_to_receive = sorted(receivers, key=lambda c: c["resources"]["energy"]["max"])

        shuffling_tuples = list()
        for app in donors:
            diff = app["resources"]["energy"]["max"] - app["resources"]["energy"]["usage"]
            stolen_amount = self.__ENERGY_STOLEN_PERCENTAGE * diff
            shuffling_tuples.append((app, stolen_amount))
        shuffling_tuples = sorted(shuffling_tuples, key=lambda c: c[1])

        # Give the resources to the bottlenecked applications
        for receiver in apps_to_receive:

            if shuffling_tuples:
                donor, amount_to_scale = shuffling_tuples.pop(0)
            else:
                MyUtils.log_info("No more donors, app {0} left out".format(receiver["name"]), self.__debug)
                continue

            donor["resources"]["energy"]["max"] -= amount_to_scale
            receiver["resources"]["energy"]["max"] += amount_to_scale

            MyUtils.update_structure(donor, self.__couchdb_handler, self.__debug)
            MyUtils.update_structure(receiver, self.__couchdb_handler, self.__debug)

            MyUtils.log_info(
                "Energy swap between {0}(donor) and {1}(receiver)".format(donor["name"], receiver["name"]),
                self.__debug)

    def rebalance_applications(self, config):
        self.__config = config
        self.__debug = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "DEBUG")
        self.__ENERGY_DIFF_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_DIFF_PERCENTAGE")
        self.__ENERGY_STOLEN_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_STOLEN_PERCENTAGE")
        MyUtils.log_info("_______________", self.__debug)
        MyUtils.log_info("Performing APP ENERGY Balancing", self.__debug)
        MyUtils.log_info("ENERGY_DIFF_PERCENTAGE -> {0}".format(self.__ENERGY_DIFF_PERCENTAGE), self.__debug)
        MyUtils.log_info("ENERGY_STOLEN_PERCENTAGE -> {0}".format(self.__ENERGY_STOLEN_PERCENTAGE), self.__debug)

        try:
            applications = MyUtils.get_structures(self.__couchdb_handler, self.__debug, subtype="application")
            users = self.__couchdb_handler.get_users()
        except requests.exceptions.HTTPError as e:
            MyUtils.log_error("Couldn't get users and/or applications", self.__debug)
            MyUtils.log_error(str(e), self.__debug)
            return

        for user in users:
            MyUtils.log_info("Processing user {0}".format(user["name"]), self.__debug)
            user_apps = get_user_apps(applications, user)
            if "energy_policy" not in user:
                balancing_policy = "static"
                MyUtils.log_info(
                    "Energy balancing policy unset for user {0}, defaulting to 'static'".format(user["name"]),
                    self.__debug)
            else:
                balancing_policy = user["energy_policy"]

            MyUtils.log_info("User {0} has {1} policy".format(user["name"], balancing_policy), self.__debug)
            if balancing_policy == "static":
                max_energy = user["energy"]["max"]
                self.__static_app_rebalancing(user_apps, max_energy)

            elif balancing_policy == "dynamic":
                self.__dynamic_app_rebalancing(user_apps)

            else:
                MyUtils.log_error("Unknown energy balancing policy '{0}'".format(balancing_policy), self.__debug)

        MyUtils.log_info("_______________\n", self.__debug)

Classes

class ApplicationRebalancer
Expand source code
class ApplicationRebalancer:
    def __init__(self):
        self.__couchdb_handler = couchdb.CouchDBServer()
        self.__debug = True
        self.__config = {}


    def __app_energy_can_be_rebalanced(self, application):
        return app_can_be_rebalanced(application, "application", self.__couchdb_handler)

    def __static_app_rebalancing(self, applications, max_energy):
        # Add up all the shares
        total_shares = sum([app["resources"]["energy"]["shares"] for app in applications])

        # For each application calculate the energy according to its shares
        for app in applications:
            percentage = app["resources"]["energy"]["shares"] / total_shares

            limit_energy = int(max_energy * percentage)
            current_energy = app["resources"]["energy"]["max"]

            # If the database record and the new limit are not the same, update
            if current_energy != limit_energy:
                app["resources"]["energy"]["max"] = limit_energy
                self.__couchdb_handler.update_structure(app)
                MyUtils.log_info("Updated energy limit of app {0}".format(app["name"]), self.__debug)

            # MyUtils.log_info("Processed app {0} with an energy share of {1}% and {2} Watts".format(
            #     app["name"], str("%.2f" % (100 * percentage)), limit_energy), self.__debug)

    def __dynamic_app_rebalancing(self, applications):
        donors, receivers = list(), list()
        for app in applications:
            if self.__app_energy_can_be_rebalanced(app):
                diff = app["resources"]["energy"]["max"] - app["resources"]["energy"]["usage"]
                if diff > self.__ENERGY_DIFF_PERCENTAGE * app["resources"]["energy"]["max"]:
                    donors.append(app)
                else:
                    receivers.append(app)

        if not receivers:
            MyUtils.log_info("No app to give energy shares", self.__debug)
            return
        else:
            # Order the apps from lower to upper energy limit
            apps_to_receive = sorted(receivers, key=lambda c: c["resources"]["energy"]["max"])

        shuffling_tuples = list()
        for app in donors:
            diff = app["resources"]["energy"]["max"] - app["resources"]["energy"]["usage"]
            stolen_amount = self.__ENERGY_STOLEN_PERCENTAGE * diff
            shuffling_tuples.append((app, stolen_amount))
        shuffling_tuples = sorted(shuffling_tuples, key=lambda c: c[1])

        # Give the resources to the bottlenecked applications
        for receiver in apps_to_receive:

            if shuffling_tuples:
                donor, amount_to_scale = shuffling_tuples.pop(0)
            else:
                MyUtils.log_info("No more donors, app {0} left out".format(receiver["name"]), self.__debug)
                continue

            donor["resources"]["energy"]["max"] -= amount_to_scale
            receiver["resources"]["energy"]["max"] += amount_to_scale

            MyUtils.update_structure(donor, self.__couchdb_handler, self.__debug)
            MyUtils.update_structure(receiver, self.__couchdb_handler, self.__debug)

            MyUtils.log_info(
                "Energy swap between {0}(donor) and {1}(receiver)".format(donor["name"], receiver["name"]),
                self.__debug)

    def rebalance_applications(self, config):
        self.__config = config
        self.__debug = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "DEBUG")
        self.__ENERGY_DIFF_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_DIFF_PERCENTAGE")
        self.__ENERGY_STOLEN_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_STOLEN_PERCENTAGE")
        MyUtils.log_info("_______________", self.__debug)
        MyUtils.log_info("Performing APP ENERGY Balancing", self.__debug)
        MyUtils.log_info("ENERGY_DIFF_PERCENTAGE -> {0}".format(self.__ENERGY_DIFF_PERCENTAGE), self.__debug)
        MyUtils.log_info("ENERGY_STOLEN_PERCENTAGE -> {0}".format(self.__ENERGY_STOLEN_PERCENTAGE), self.__debug)

        try:
            applications = MyUtils.get_structures(self.__couchdb_handler, self.__debug, subtype="application")
            users = self.__couchdb_handler.get_users()
        except requests.exceptions.HTTPError as e:
            MyUtils.log_error("Couldn't get users and/or applications", self.__debug)
            MyUtils.log_error(str(e), self.__debug)
            return

        for user in users:
            MyUtils.log_info("Processing user {0}".format(user["name"]), self.__debug)
            user_apps = get_user_apps(applications, user)
            if "energy_policy" not in user:
                balancing_policy = "static"
                MyUtils.log_info(
                    "Energy balancing policy unset for user {0}, defaulting to 'static'".format(user["name"]),
                    self.__debug)
            else:
                balancing_policy = user["energy_policy"]

            MyUtils.log_info("User {0} has {1} policy".format(user["name"], balancing_policy), self.__debug)
            if balancing_policy == "static":
                max_energy = user["energy"]["max"]
                self.__static_app_rebalancing(user_apps, max_energy)

            elif balancing_policy == "dynamic":
                self.__dynamic_app_rebalancing(user_apps)

            else:
                MyUtils.log_error("Unknown energy balancing policy '{0}'".format(balancing_policy), self.__debug)

        MyUtils.log_info("_______________\n", self.__debug)

Methods

def rebalance_applications(self, config)
Expand source code
def rebalance_applications(self, config):
    self.__config = config
    self.__debug = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "DEBUG")
    self.__ENERGY_DIFF_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_DIFF_PERCENTAGE")
    self.__ENERGY_STOLEN_PERCENTAGE = MyUtils.get_config_value(self.__config, CONFIG_DEFAULT_VALUES, "ENERGY_STOLEN_PERCENTAGE")
    MyUtils.log_info("_______________", self.__debug)
    MyUtils.log_info("Performing APP ENERGY Balancing", self.__debug)
    MyUtils.log_info("ENERGY_DIFF_PERCENTAGE -> {0}".format(self.__ENERGY_DIFF_PERCENTAGE), self.__debug)
    MyUtils.log_info("ENERGY_STOLEN_PERCENTAGE -> {0}".format(self.__ENERGY_STOLEN_PERCENTAGE), self.__debug)

    try:
        applications = MyUtils.get_structures(self.__couchdb_handler, self.__debug, subtype="application")
        users = self.__couchdb_handler.get_users()
    except requests.exceptions.HTTPError as e:
        MyUtils.log_error("Couldn't get users and/or applications", self.__debug)
        MyUtils.log_error(str(e), self.__debug)
        return

    for user in users:
        MyUtils.log_info("Processing user {0}".format(user["name"]), self.__debug)
        user_apps = get_user_apps(applications, user)
        if "energy_policy" not in user:
            balancing_policy = "static"
            MyUtils.log_info(
                "Energy balancing policy unset for user {0}, defaulting to 'static'".format(user["name"]),
                self.__debug)
        else:
            balancing_policy = user["energy_policy"]

        MyUtils.log_info("User {0} has {1} policy".format(user["name"], balancing_policy), self.__debug)
        if balancing_policy == "static":
            max_energy = user["energy"]["max"]
            self.__static_app_rebalancing(user_apps, max_energy)

        elif balancing_policy == "dynamic":
            self.__dynamic_app_rebalancing(user_apps)

        else:
            MyUtils.log_error("Unknown energy balancing policy '{0}'".format(balancing_policy), self.__debug)

    MyUtils.log_info("_______________\n", self.__debug)