Mini Shell
Direktori : /usr/share/lve/dbgovernor/ |
|
Current File : //usr/share/lve/dbgovernor/governor_package_limitting.py |
#!/opt/cloudlinux/venv/bin/python3
# coding:utf-8
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2024 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
#
import argparse
import functools
import itertools
import json
import logging
import os
import subprocess
import sys
import time
from typing import Dict, List, Tuple
from clcommon.cpapi import cpinfo, admin_packages, resellers_packages, cpusers, getCPName
from utilities import acquire_lock, LockFailedException
CURRENT_PROGRAM_NAME = os.path.basename(sys.argv[0])
DBCTL_BIN = '/usr/share/lve/dbgovernor/utils/dbctl_orig'
PACKAGE_LIMIT_CONFIG = '/etc/container/governor_package_limit.json'
LOCK_FILE = '/var/run/governor_package_limit'
DBCTL_SYNC_LOCK_FILE = '/var/run/governor_package_sync'
DEBUG = False
ENCODING = 'utf-8'
def init_logging():
"""
Messages related to governor_package_limitting
"""
logging.basicConfig(
stream = sys.stderr,
format=f"{CURRENT_PROGRAM_NAME} %(levelname)s: %(message)s",
level=logging.ERROR
)
def _process_call_error(call_err):
if isinstance(call_err.cmd, list):
cmd_str = " ".join(call_err.cmd)
else:
cmd_str = call_err.cmd
logging.error(f'command \'{cmd_str}\' exit code = {call_err.returncode}')
logging.info('stdout:\n' + str(call_err.stdout))
if call_err.stderr is not None:
logging.info('stderr:\n' + str(call_err.stderr))
return call_err.returncode
def debug_log(line, end='\n'):
"""
Debug output log
"""
global DEBUG
if DEBUG:
print(line, end=end)
def build_parser():
"""
Build CLI parser
"""
parser = argparse.ArgumentParser(
prog="Configure mysqlgovernor limits with Control Panel Packages",
description="Description: Configure governor-mysql with Control Panel Package limits",
add_help=False,
usage='governor_package_limitting.py [COMMAND] [OPTIONS]'
)
parser._positionals.title = 'Commands'
parser._optionals.title = 'Options'
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
help='Governor package limiting')
parser.add_argument('--debug', action='store_true', help='Turn on debug mode')
subparser = parser.add_subparsers(dest='command')
################ SET ################
_set = subparser.add_parser(
'set',
help='Set limits for governor packages limits',
description='Description: Set cpu limit for governor package'
)
_set.add_argument('--package', help='Package name', type=str, required=True)
_set.add_argument('--cpu', help='limit CPU (pct) usage', nargs='+',)
_set.add_argument('--read', help='limit READ (MB/s) usage', nargs='+')
_set.add_argument('--write', help='limit WRITE (MB/s) usage', nargs='+')
_set.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ DELETE ################
delete = subparser.add_parser(
'delete',
help='Delete governor package limits',
description='Description: Delete governor package limit',
usage='governor_package_limitting.py delete [PACKAGE_NAME]'
)
delete._optionals.title = 'Options'
delete.add_argument('--package', help='Package name', type=str, required=True)
delete.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ GET ################
get = subparser.add_parser(
'get',
help='Get governor package limits',
description='Description: Get governor package limits',
usage='governor_package_limitting.py get [OPTIONS]',
formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999)
)
get._optionals.title = 'Options'
g = get.add_mutually_exclusive_group()
g.add_argument('--package', help='Get package limits', type=str, )
g.add_argument('--all', help="Get all package limits", action='store_true')
get.add_argument('--format', help='Show limits in formats: (KB/s), (BB/s), (MB/s). Default is mb',
default='mb',choices=['bb', 'kb', 'mb'])
get._optionals.title = 'Options'
get.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ GET INDIVIDUAL ################
get_individual = subparser.add_parser(
'get_individual',
help='Get governor package limits vector',
description='The vector describes which limit is an individual and which is not',
usage='governor_package_limitting.py get_individual --user=[USER_NAME]'
)
get_individual._optionals.title = 'Options'
get_individual.add_argument('--user', help='Shows a particular user\'s vector', type=str,)
get_individual.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ SET INDIVIDUAL ################
_set_individual = subparser.add_parser(
'set_individual',
help='Set governor package limits vector',
description='The vector describes which limit is an individual and which is not',
usage='governor_package_limitting.py set_individual --user=[USER_NAME] --cpu=[False]*4 --read=[True]*4 --write=[True]*4'
)
_set_individual.add_argument('--user', help='Package name', type=str, required=True)
_set_individual.add_argument('--cpu', help='True/False vector, four values', nargs='+',)
_set_individual.add_argument('--read', help='True/False vector, four values', nargs='+')
_set_individual.add_argument('--write', help='True/False vector, four values', nargs='+')
_set_individual.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ RESET INDIVIDUAL ################
reset_individual = subparser.add_parser(
'reset_individual',
help='Erases information about individual limits',
description='The user will use a package limits or default limits as result',
usage='governor_package_limitting.py reset_individual --user=[USER_NAME] --limits=mysql-io,mysql-cpu'
)
reset_individual._optionals.title = 'Options'
reset_individual.add_argument('--user', help='User name', type=str, required=True)
reset_individual.add_argument('--limits', help='mysql-io or mysql-cpu', type=str)
reset_individual.add_argument('--debug', action='store_true', help='Turn on debug mode')
################ SYNC ################
sync = subparser.add_parser(
'sync',
help='Synchronize governor package limits',
description='Description: Apply package limitting configuration to governor',
usage='governor_package_limitting.py sync'
)
sync._optionals.title = 'Options'
sync.add_argument('--package', help='Package name', type=str, required=False)
sync.add_argument('--user', help='User name', type=str, required=False)
sync.add_argument('--debug', action='store_true', help='Turn on debug mode')
return parser
def cp_packages() -> Dict[str, list]:
"""
Get packages and it's users.
Returns:
(dict): package_name and contained users list
"""
def update_list_in_a_dict_item(package: str, user: str) -> None:
"""
Updates a list of users who use the package
"""
if users_and_packages.get(package):
users_and_packages[package].append(user)
else:
users_and_packages[package] = [user]
__cpinfo = cpinfo()
users_and_packages = {}
# user's packages
for i in __cpinfo:
update_list_in_a_dict_item(i[1], i[0])
# reseller's packages
for usr, pkgs in resellers_packages().items():
for pkg in pkgs:
update_list_in_a_dict_item(pkg, usr)
return users_and_packages
def limits_serializer(args: List, set_vector=False):
"""Convert user input list of string to list of int
<set_vector=True> -> when use the function to
prepare vector instead of values
For example: ['1,2,3,4'] to -> [1,2,3,4]
"""
__limits = []
try:
if args and isinstance(args, list):
for i in args[0].split(','):
if set_vector and 'true' in i.lower():
__limits.append(True)
elif set_vector and 'false' in i.lower():
__limits.append(False)
else:
__limits.append(i)
except Exception as err:
logging.error(f'Some parameters are incorrect: {err}')
sys.exit(1)
if len(__limits) > 4:
logging.error('Some parameters are incorrect. Provided options is more than 4')
sys.exit(1)
if __limits and set_vector and len(__limits) != 4:
logging.error('Some parameters are incorrect. Provided options must be equal 4 when you set the vector!')
sys.exit(1)
if not __limits and set_vector:
while len(__limits) < 4:
__limits.append(False)
if len(__limits) < 4:
while len(__limits) < 4:
__limits.append(0)
return __limits
def write_config_to_json_file(cfg: dict):
with acquire_lock(LOCK_FILE, exclusive=True):
with open(PACKAGE_LIMIT_CONFIG, 'w', encoding=ENCODING) as jsonfile:
json.dump(cfg, jsonfile)
def read_config_file():
with acquire_lock(LOCK_FILE, exclusive=True):
with open(PACKAGE_LIMIT_CONFIG, 'r', encoding=ENCODING) as jsonfile:
try:
cfg = json.load(jsonfile)
except json.JSONDecodeError:
return {}
return cfg if cfg else {}
def get_item_from_config_dict(name: str, cfg: dict):
"""Get package from cfg dictionary if exists"""
cfg = cfg.get(name)
if cfg:
cfg = {name: cfg}
return cfg
def show_default_value_instead_of_zero(name: str, lim_list: list,
default_limits: tuple):
"""
Replaces 'zero' values by default values
Args:
name - cpu or read or write set of limits
lim_list - lim_list is a current set of limits we'll modify if 'zero'
values are present
default_limits - 'default limits' we'll use instead of 'zero' values
"""
d_cpu, d_read, d_write = default_limits
d_limits = {'cpu': d_cpu, 'read': d_read, 'write': d_write}
# to know which values have not been changed
indices_of_unchanged_elements = list()
for _indx, num in enumerate(lim_list):
if num < 1:
lim_list[_indx] = d_limits[name][_indx]
else:
indices_of_unchanged_elements.append(_indx)
return indices_of_unchanged_elements
def print_individual_in_json_format(cfg: dict):
print(json.dumps(cfg, ensure_ascii=False))
def print_config_in_json_format(cfg: dict, size_format, only_section=True):
default_limits = get_dbctl_limits(format=size_format)
if only_section:
cfg = cfg['package_limits']
for key, val in cfg.items():
for k, v in val.items():
# returns the default values instead of 'zero'
index_list = show_default_value_instead_of_zero(
k, v, default_limits)
if k != 'cpu':
for i in index_list:
cfg[key][k][i] = byte_size_convertor(
v[i], from_format='bb', to_format=size_format
)
print(json.dumps(cfg, ensure_ascii=False))
def check_if_values_are_not_less_than_zero(cfg):
for k, v in cfg.items():
for i in v:
if i < 0:
print(f'Value for {k} must be >= 0')
debug_log(f'Incorrect parameters for {k}')
debug_log(f'Applied config is {cfg}')
sys.exit(1)
def fill_gpl_json(entity_name: str, cpu: list = None, io_read: list = None,
io_write: list = None, serialize: bool = True,
set_vector: bool = False) -> None:
"""
Setting 'package limits' or 'individual limits' to
the governor_package_limit.json
Args:
entity_name (str): Package or user name
cpu (list): cpu limits
io_read (list): io read limits
io_write (list): io write limits
serialize (bool): To serialize or not
set_vector (bool): True if setting individual limits
Return:
None
"""
# check package existence
if not set_vector and entity_name not in get_all_packages():
logging.error(f'Package name {entity_name} not found in {getCPName()} packages')
sys.exit(1)
# check user existence
if set_vector and entity_name not in cpusers():
logging.error(f'User {entity_name} not found in {getCPName()} users')
sys.exit(1)
cfg = {
entity_name: {
'cpu': limits_serializer(cpu, set_vector) if serialize else cpu,
'read': limits_serializer(io_read, set_vector) if serialize else io_read,
'write': limits_serializer(io_write, set_vector) if serialize else io_write,
}
}
if not set_vector:
convert_io_rw_to_bb(cfg[entity_name])
check_if_values_are_not_less_than_zero(cfg[entity_name])
section = 'package_limits'
else:
section = 'individual_limits'
config = get_package_limit()
if config[section].get(entity_name):
if cpu:
config[section][entity_name]['cpu'] = cfg[entity_name]['cpu']
if io_read:
config[section][entity_name]['read'] = cfg[entity_name]['read']
if io_write:
config[section][entity_name]['write'] = cfg[entity_name]['write']
else:
config[section][entity_name] = cfg[entity_name]
debug_log(f'Setting package limit with config: {config}\n')
write_config_to_json_file(config)
return
def update_default_limits():
"""
Update default values in our json from the dbctl list
"""
config = get_package_limit()
try:
config['package_limits'].update({'default': {"cpu": [0] * 4,
"read": [0] * 4,
"write": [0] * 4}
}
)
write_config_to_json_file(config)
except (KeyError, AttributeError):
return
def delete_package_limit(package: str):
"""Delete package limits
Args:
package (str): Name of package limit to delete
Returns:
None
"""
config = get_package_limit()
try:
config['package_limits'].pop(package)
write_config_to_json_file(config)
debug_log(f'Deleting package {package} from config')
except (KeyError, AttributeError) as err:
logging.error(f'Package name {package} not found')
debug_log(err)
sys.exit(1)
def reset_individual(username: str, certain_limits: str = None):
"""
Delete user individual limits vector
from the governor_package_limit.json, 'individual_limits' section
"""
help_msg = f"""Vector {username} not found"""
def reset_all():
# remove the individual limits from the mysql-governor.xml
run_dbctl_command([username], 'delete')
config = get_package_limit()
try:
config['individual_limits'].pop(username)
write_config_to_json_file(config)
debug_log(f'Deleting vector {username} from config')
except (KeyError, AttributeError) as err:
logging.error(help_msg)
debug_log(err)
sys.exit(1)
if not certain_limits or certain_limits.lower() == 'all':
reset_all()
else:
_limits = certain_limits.split(',')
if len(_limits) > 1:
reset_all()
else:
reset_a_certain_limit(username, _limits)
def change_vector_for_a_user(_user: str, limits: list):
"""
Marks that a particular limits are not individual anymore.
We can reset individual limits for mysql-cpu or mysql-io.
Args:
_user: is a particular user we makes changes for
limits: vector where non individual limits have to be set
"""
config = get_package_limit()
dictionary_to_update = {'mysql-cpu': {'cpu': [False] * 4},
'mysql-io': {'read': [False] * 4,
'write': [False] * 4}
}
for lim in limits:
update_vector = dictionary_to_update.get(lim)
try:
config['individual_limits'][_user].update(update_vector)
except (KeyError, AttributeError) as err:
return
write_config_to_json_file(config)
def return_the_individual_limits_to_dbctl(_user: str, limits: list,
saved_limits: tuple):
"""
Sets the individual limits as they were before because
the individual limits must not be changed
Args:
_user: the user for which individual limits should be set
limits: mysql-io (read/write) or mysql-cpu set of limits
saved_limits: individual limits that were set before the
general reset of limits for a particular user
"""
_cpu_limit, _read_limit, _write_limit = saved_limits
for lim in limits:
# if we change mysql read/write limits - the individual cpu limits
# must not be changed!
if lim == 'mysql-io':
_cpu_limits = ",".join([str(i) + 'b' for i in _cpu_limit])
_limit = f'--cpu={_cpu_limits}'
# the same situation as described above
elif lim == 'mysql-cpu':
_read_limit = ",".join([str(i) + 'b' for i in _read_limit])
_read = f'--read={_read_limit}'
_write_limit = ",".join([str(i) + 'b' for i in _write_limit])
_write = f'--write={_write_limit}'
_limit = f'{_read} {_write}'
else:
return
dbctlset = f'{DBCTL_BIN} set {_user} {_limit}'
try:
subprocess.run(dbctlset, shell=True, text=True, check=True, capture_output=True)
except subprocess.CalledProcessError as call_err:
return _process_call_error(call_err)
def reset_a_certain_limit(username: str, limits: list):
"""
Makes possible to reset the individual limits for a particular limit.
For instance we can reset only mysql-cpu or only mysql-io (read/write),
not both.
"""
# Saves current individual limits
saved_limits = get_dbctl_limits(username)
# Removes all limits (dbctl individual) for a particular user
run_dbctl_command([username], 'delete')
# Marks non individual limits (set as <False>)
# governor_package_limit.json -> individual_limits ->
# user name -> cpu or read/write
change_vector_for_a_user(username, limits)
# Return individual limits for a set of limits which still individual
return_the_individual_limits_to_dbctl(username, limits, saved_limits)
def get_package_limit(package: str = None, size_format: str = 'mb',
print_to_stdout=False, cfg=None):
"""Get package limits
Args:
package (str): name of package to get
print_to_stdout (bool): Print to stdout is used for cli.
size_format: Print values in specified format. mb, kb, bb
Returns:
Package limit configurations or provided package configuration
"""
cfg = cfg or read_config_file()
if package:
cfg = get_item_from_config_dict(package, cfg['package_limits'])
only_section = False
else:
only_section = True
if print_to_stdout and cfg:
print_config_in_json_format(cfg, size_format, only_section)
return cfg
def get_individual(username: str = None, print_to_stdout=False):
"""
Get individual limits
Args:
username: name of user to get
print_to_stdout (bool): Print to stdout is used for cli.
Returns:
Individual limits vector ->
{"individual_limits": {"user1": {"cpu": [false, false, false, false],
"read": [true, true, true, true],
"write": [true, true, true, true]}}}
"""
cfg = read_config_file()
if username:
cfg = get_item_from_config_dict(username, cfg['individual_limits'])
if not cfg:
cfg = {username: {'cpu': [False] * 4,
'read': [False] * 4,
'write': [False] * 4}
}
else:
cfg = cfg['individual_limits']
if print_to_stdout:
print_individual_in_json_format(cfg)
return cfg
def run_dbctl_command(users: list, action: str, limits: dict = None) -> int:
"""Run dbctl command in os, return exit code
Set or delete limits for all users specified in package
Args:
users (list): List of users to apply configuration
action (str): Set or delete. Set is used both for set and update.
limits (dict): cpu, read, write in format [int]
"""
return_code = 0
if action == 'set' and not limits:
logging.error("Limits for dbctl have not been set")
sys.exit(1)
if action == 'delete' and users:
for user in users:
command = f'{DBCTL_BIN} delete {user}'
debug_log(f'Running command: {command}')
try:
command_result = subprocess.run(command, shell=True, text=True, check=True, capture_output=True)
return_code += command_result.returncode
except subprocess.CalledProcessError as call_err:
# we shouldn't do exit here because this function is called in loop in dbctl_sync
return_code += _process_call_error(call_err)
if action == 'set' and limits and users:
for user in users:
# Returns an empty list when user is absent in dbctl
# so we need to skip all dbctl actions
prepare_limits_out = prepare_limits(
user, package_limit=limits,
individual_limit=get_individual(user)
)
if prepare_limits_out:
cpu, io_read, io_write = prepare_limits_out
command = f'{DBCTL_BIN} set {user} --cpu={cpu} --read={io_read} --write={io_write}'
debug_log(f'Running command: {command}')
try:
command_result = subprocess.run(command, shell=True, text=True, check=True, capture_output=True)
return_code += command_result.returncode
except subprocess.CalledProcessError as call_err:
# we shouldn't do exit here because this function is called in loop in dbctl_sync
return_code += _process_call_error(call_err)
return return_code
def dbctl_sync(action: str, package: str = None, user: str = None):
"""Sync package configuration with dbgovernor
Args:
action (str): Set or Delete
package (str): Package name is used with action delete.
package (str): User name is used with action 'set'
to synchronise for a specific user only
"""
if not action:
logging.error("sync action not specified")
sys.exit(1)
exit_code = 0
debug_log("Syncing with dbctl")
__cp_packages = cp_packages()
if action == 'delete' and package:
users_to_apply_package = __cp_packages.get(package)
if users_to_apply_package:
debug_log(f'Deleting package limits for users: {users_to_apply_package}')
exit_code += run_dbctl_command(users_to_apply_package, action)
sys.exit(exit_code)
if action == 'set':
_package_limits = get_package_limit(package)
# Use a specific package (including all users of these package)
# if package for sync is specified
# Use all packages otherwise
package_limits = _package_limits if package else _package_limits['package_limits']
# To avoid traceback if incorrect package name has been set
if not package_limits:
logging.warning(f'package limits for \'{package}\' are empty, probably incorrect package name')
return
for package_name, limits in package_limits.items():
users_to_apply_package = __cp_packages.get(package_name)
if users_to_apply_package:
if not user:
# Sets package limits for all package's users
debug_log(f'Setting package limits for users: {users_to_apply_package}')
exit_code += run_dbctl_command(users_to_apply_package, action, limits)
continue
if user in users_to_apply_package:
# Sets package limits only for a specific user
# if user for sync is specified
debug_log(f'Setting package limits for user: {user}')
exit_code += run_dbctl_command([user], action, limits)
sys.exit(exit_code)
def byte_size_convertor(value: int, from_format: str, to_format: str):
"""Converting integer between formats mb|kb|bb
Args:
value (int): Integer value to convert
from_format (str): To convert from mb|kb|bb
to_format (str): To convert to mb|kb|bb
"""
if value < 0:
return value
if from_format == 'bb':
if to_format == 'kb':
value /= 1024
elif to_format == 'mb':
value = value / 1024 / 1024
elif from_format == 'kb':
if to_format == 'bb':
value *= 1024
elif to_format == 'mb':
value /= 1024
elif from_format == 'mb':
if to_format == 'kb':
value *= 1024
elif to_format == 'bb':
value = value * 1024 * 1024
return int(value)
def convert_io_rw_to_bb(cfg: dict):
"""Converting MB or KB to bytes
Parameters from user cli can be bytes, MB or KB.
Symbol 'b' - bytes, symbol 'k' - kilobytes,
symbol 'm' or no symbols - megabytes
[1, 50m, 52428800b, 100k]
Args:
cfg (dict): {cpu: [x,x,x,x], read: [x,x,x,x], write: [x,x,x,x,]}
Returns:
cfg (dict)
"""
for k, v in cfg.items():
if k != 'cpu':
for i in range(4):
if isinstance(v[i], str):
if 'b' in v[i]:
v[i] = int(v[i].replace('b', ''))
elif 'k' in v[i]:
debug_log(f'Converting kilobytes `{v[i]}` to bytes')
v[i] = byte_size_convertor(
int(v[i].replace('k', '')),
from_format='kb', to_format='bb'
)
elif 'm' in v[i]:
debug_log(f'Converting megabytes `{v[i]}` to bytes')
v[i] = byte_size_convertor(
int(v[i].replace('m', '')),
from_format='mb', to_format='bb'
)
else:
debug_log(f'Converting megabytes `{v[i]}` to bytes')
v[i] = byte_size_convertor(
int(v[i]),
from_format='mb', to_format='bb'
)
else:
for i in range(4):
v[i] = int(v[i])
debug_log('\n')
return cfg
def trying_to_get_user_in_dbctl_list(user: str, format: str) -> Dict:
"""
Extract user limits from dbctl list output
Make additional attempt in case of fail and fallback to default limits
Args:
user -> web panel user for which we perform actions
format -> formats that we use in dbctl (bb, kb, mb)
Returns:
specific user limits (cpu, read, write)
"""
data = _get_dbctl_list_json(format).get(user)
# make one more attempt in case of new user that wasn't added to dbctl list yet
if not data:
default_limits = {
"cpu": {"current": 0, "short": 0, "mid": 0, "long": 0},
"read": {"current": 0, "short": 0, "mid": 0, "long": 0},
"write": {"current": 0, "short": 0, "mid": 0, "long": 0}
}
# restart db_governor service to add the user to the dbctl list immediately
os.system('/usr/share/lve/dbgovernor/mysqlgovernor.py --dbupdate')
time.sleep(1)
os.system('service db_governor restart &> /dev/null')
time.sleep(1)
# if user limits still unavailable - just return a structure with zero values
data = _get_dbctl_list_json(format).get(user) or default_limits
return data
def _get_dbctl_list_json(format: str) -> Dict:
dbctl_limits = f'{DBCTL_BIN} list-json --{format}'
output = subprocess.run(dbctl_limits, shell=True,
text=True, capture_output=True)
_data = output.stdout
try:
data = json.loads(_data)
except json.JSONDecodeError:
logging.debug(f"<{DBCTL_BIN} list-json --{format}> returns non json data!")
logging.debug(f"stdout -> {output.stdout}")
logging.debug(f"stderr -> {output.stderr}")
sys.exit(1)
return data
def get_dbctl_limits(user: str = 'default', format: str = 'bb') -> Tuple:
"""
Gets dbctl limits list and returns a tuple of individual limits
(cpu, read, write) for a specific user.
Running this function without 'user' argument returns
the default limits.
Args: user -> to get an individual user limits, returns 'default'
if hasn't been set
format -> all available formats we use in dbctl:
bb - bytes
kb - kilobytes
mb - megabytes
Returns: a tuple of lists ([cpu],[read],[write]),
or blank tuple if user has not been found
"""
individual_limits = trying_to_get_user_in_dbctl_list(user, format)
individual_cpu_limit = (
individual_limits['cpu']['current'],
individual_limits['cpu']['short'],
individual_limits['cpu']['mid'],
individual_limits['cpu']['long']
)
individual_read_limit = (
individual_limits['read']['current'],
individual_limits['read']['short'],
individual_limits['read']['mid'],
individual_limits['read']['long']
)
individual_write_limit = (
individual_limits['write']['current'],
individual_limits['write']['short'],
individual_limits['write']['mid'],
individual_limits['write']['long']
)
debug_log(f'{user}\'s individual limits are: ', end='')
debug_log(f'cpu: {individual_cpu_limit}, read: {individual_read_limit}, write: {individual_write_limit}')
return individual_cpu_limit, individual_read_limit, individual_write_limit
def get_the_limits_set(limits_set: list) -> Tuple:
"""
Here we have twelve values:
-> four per cpu
-> four per read
-> four per write
Here we divide them to conviniate representation
"""
cpu = tuple(limits_set[0:4])
read = tuple(limits_set[4:8])
write = tuple(limits_set[8:])
return cpu, read, write
def set_default_limit(package_limit: dict) -> Tuple:
"""
Defines the limits (cpu, read, write). Uses package limits or
default limits depending on values. If package value is greater
than zero - use it, otherwise use default value.
"""
package_cpu_limit = package_limit.get('cpu')
package_read_limit = package_limit.get('read')
package_write_limit = package_limit.get('write')
all_limits = list()
for tuple_of_limits_lists in (package_cpu_limit,
package_read_limit,
package_write_limit):
for n in tuple_of_limits_lists:
# values of package limit and default limit (0) respectively
all_limits.append(n) if n > 0 else all_limits.append(0)
return get_the_limits_set(all_limits)
def ensures_the_individual_limits_still_set(
vector: dict, default_or_package_limit: tuple,
individual_limits: tuple) -> Tuple:
"""
Sets the individual limit instead of <package limit/default limit>
if vector value is <True> for the certain limit.
"""
all_limits = list()
individual_cpu_limit, individual_read_limit, individual_write_limit = individual_limits
dorp_cpu_limit, dorp_read_limit, dorp_write_limit = default_or_package_limit
vectors = vector.values()
for vector_dict in vectors:
cpu_vectors = vector_dict['cpu']
read_vectors = vector_dict['read']
write_vectors = vector_dict['write']
# <data_set> contains a 'vector list', an 'individual limits' list
# and a current list of limits collected from the package limits
# or default limits
for data_set in (
(cpu_vectors, individual_cpu_limit, dorp_cpu_limit),
(read_vectors, individual_read_limit, dorp_read_limit),
(write_vectors, individual_write_limit, dorp_write_limit)
):
for n in zip(data_set[0], data_set[1], data_set[2]):
# If vector is True
if n[0]:
all_limits.append(n[1])
# If vector is False
else:
all_limits.append(n[2])
return get_the_limits_set(all_limits)
def prepare_limits(user: str, package_limit: Dict, individual_limit: Dict) -> List:
"""Prepare Limits with algorithm
First of all sets the package limits or default limits,
than sets the individual limits (dependin on vector values)
For example:
Pack1 Package limit for cpu is: [100, 0, 65, 40]
Default Package limit is: [50, 90, 0, 50]
Individual limit is: [150, not_set, not_set, not_set]
Result will be: [150, 90, 65, 40]
"""
cpu_limit, read_limit, write_limit = ensures_the_individual_limits_still_set(
individual_limit,
set_default_limit(package_limit),
get_dbctl_limits(user)
)
try:
# with 'b' symbol to set bytes using 'dbctl' tool
cpu = ','.join(str(x) for x in cpu_limit)
io_read = ','.join(str(x) + 'b' for x in read_limit)
io_write = ','.join(str(x) + 'b' for x in write_limit)
debug_log(f'Limits after calculation for {user} is:')
debug_log(f'cpu: [{cpu}], read: [{io_read}], write: [{io_write}]')
return cpu, io_read, io_write
except TypeError as err:
logging.error(f'Some limits are not given: {err}')
sys.exit(1)
@functools.cache
def get_all_packages():
"""
Gets all packages: admin packages and resellers packages either.
"""
all_packages = set(admin_packages())
reseller_packages_iter = itertools.chain.from_iterable(resellers_packages().values())
all_packages.update(reseller_packages_iter)
return list(all_packages)
def sync_with_panel():
# won't do MYSQLG-789
"""
Just getting package names and applying default values [0,0,0,0]
"""
cfg = read_config_file()
for package in get_all_packages():
if not get_package_limit(package, cfg=cfg):
fill_gpl_json(
entity_name=package
)
cfg = read_config_file()
def turn_on_debug_if_user_enabled_debug_mode(opts):
if opts.debug:
global DEBUG
DEBUG = True
def ensure_json_presence():
"""
The governor_package_limit.json file must always be present!
Even if someone delete the json config and its content.
"""
content = {"package_limits": {}, "individual_limits": {}}
if not os.path.exists(PACKAGE_LIMIT_CONFIG):
write_config_to_json_file(content)
return
json_ = read_config_file()
for element in content.keys():
if not json_.get(element):
json_.update({element: {}})
write_config_to_json_file(json_)
def main(argv):
"""
Run main actions
"""
parser = build_parser()
if not argv:
parser.print_help()
sys.exit(1)
opts = parser.parse_args(argv)
turn_on_debug_if_user_enabled_debug_mode(opts)
ensure_json_presence()
init_logging()
if opts.command == 'set':
fill_gpl_json(opts.package, opts.cpu, opts.read, opts.write)
dbctl_sync('set', package=opts.package)
elif opts.command == 'get':
sync_with_panel()
update_default_limits()
get_package_limit(opts.package, opts.format, print_to_stdout=True)
elif opts.command == 'delete':
delete_package_limit(opts.package)
dbctl_sync('delete', opts.package)
elif opts.command == 'sync':
# to avoid excessive calls of sync command just ignore too frequent calls
try:
with acquire_lock(DBCTL_SYNC_LOCK_FILE, exclusive=True, attempts=1):
dbctl_sync('set', opts.package, opts.user)
except LockFailedException:
debug_log('Excessive sync call ignored')
elif opts.command == 'get_individual':
get_individual(opts.user, print_to_stdout=True)
elif opts.command == 'set_individual':
fill_gpl_json(opts.user, opts.cpu, opts.read, opts.write,
serialize=True, set_vector=True)
# There is no call to 'sync' after this, since in the main case of using this command,
# 'dbctl set' is called immediately after it
# https://docs.google.com/document/d/1KH3MiHVcqJduvw6Vid8UiOvv9-6-CHDAtwkaUQh8_vU
elif opts.command == 'reset_individual':
reset_individual(opts.user, opts.limits)
dbctl_sync('set', user=opts.user)
else:
parser.print_help()
sys.exit(1)
if "__main__" == __name__:
main(sys.argv[1:])
Zerion Mini Shell 1.0