/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
cllimits
/
Upload Filee
HOME
# -*- coding: utf-8 -*- # lvectl.py - module for interfacing with lvectl utility for get/set LVE limits # # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT import copy import json import lvectllib from clcommon import cpapi from clcommon.clexception import FormattedException from cllimits.lib import exec_utility from cllimits.lib.utils import _convert_memory_value_to_adaptive_format class LvectlException(FormattedException): pass class LveCtl: """ Library for work with LVE limits """ MEGAHERZ = 1000000 GIGAHERZ = 1000000000 lve_version = None _UTILITY_PATH = "/usr/sbin/lvectl" _packages_limits = None # Panel packages limits _package_data = None # Panel LVE limits _resellers_data = None # Panel LVP limits def __init__(self): """ Object constructor. Get LVE version """ _, s_lve_version = exec_utility(self._UTILITY_PATH, ["lve-version"]) self.lve_version = int(s_lve_version) self._package_data = None # type: dict self._resellers_data = None # type: dict @staticmethod def get_panel_users_uid_list(): """ Get panel users uid list :return: List of uids """ user_packages = cpapi.list_users() uid_list = list(user_packages.keys()) uid_list.append(0) return uid_list def get_limits_by_user_id(self, user_id, human_readable_numbers=False): """ Reads limits by user id :param user_id: user/lve id :param human_readable_numbers: if True PMEM and VMEM limits in KBytes, MBytes or GBytes False - in bytes :return: dictionary """ # Load LVE limits data if need self._load_info(human_readable_numbers, userid=user_id) if user_id in self._package_data: user_limits = copy.deepcopy(self._package_data[user_id]) del user_limits["PACKAGE"] return user_limits # No limits, use default # user_limits = self._package_data[0] user_limits = copy.deepcopy(self._package_data[0]) del user_limits["PACKAGE"] return user_limits def get_limits_by_reseller_name(self, reseller_name, human_readable_numbers=False): """ Reads limits by reseller name :param reseller_name: :param: id - user/lve id :param human_readable_numbers: if True PMEM and VMEM limits in KBytes, MBytes or GBytes False - in bytes :rtype: dict """ # lvectl list-reseller <reseller_name> --json --with-name self._load_resellers_info(human_readable_numbers, reseller_name) reseller_limits = copy.deepcopy(self._resellers_data.get(reseller_name)) return reseller_limits def get_default_limits_by_reseller_name(self, reseller_name, human_readable_numbers=False): """ Reads limits by reseller name :param reseller_name: reseller's name :param human_readable_numbers: if True PMEM and VMEM limits in KBytes, MBytes or GBytes False - in bytes :rtype: dict[str, str | dict] """ if not lvectllib.lve.reseller_limit_supported(): return lvectl_args = ['list-reseller', reseller_name, '--json', '--with-name'] if not human_readable_numbers: lvectl_args.append('--bytes') ret_code, std_out = exec_utility(self._UTILITY_PATH, lvectl_args) loaded_json = json.loads(std_out) if ret_code != 0: raise LvectlException(loaded_json['msg']) default_info = loaded_json.get('data', []) if default_info: default_limits = { 'PACKAGE': 'DEFAULT', 'cpu': {'all': default_info[0]['SPEED']}, 'ep': default_info[0]['EP'], 'io': {'all': default_info[0]['IO']}, 'iops': default_info[0]['IOPS'], 'nproc': default_info[0]['NPROC'], 'pmem': _convert_memory_value_to_adaptive_format(default_info[0]['PMEM'], human_readable_numbers), 'vmem': _convert_memory_value_to_adaptive_format(default_info[0]['VMEM'], human_readable_numbers) } return default_limits def get_package_name_by_user_id(self, user_id): """ Get package name by user id. None package returns as str(None) for user checker compatibility: 'not str(None)' is True :param: id - user/lve id :return: Package name """ if self._package_data is None: from clcommon.cpapi import reseller_package_by_uid # pylint: disable=import-outside-toplevel try: package_name = str(reseller_package_by_uid(user_id)[1]) except IndexError: # No user with such uid, use empty package name package_name = '' else: if user_id in self._package_data: package_name = str(self._package_data[user_id]["PACKAGE"]) else: # default package_name = str(self._package_data[0]["PACKAGE"]) return package_name def __set_error(self, param, lve_id, err): return {'message': "%(what)s set error for uid=%(uid)s%(error)s", 'context': {'what': param, 'uid': lve_id, 'error': f" [{err}]" if err else ""}} def _set_container_limits_by_id(self, command, container_id, limits, reseller_name=None): """ Set limits for given container id :param: str command: 'set' | 'set-reseller', based on container type :param: int | str container_id: LVE | LVP id for set limits :param: dict limits: new LVE limits. Available keys: speed, vmem, pmem, mep, io, nproc, iops and 'save-all-parameters'. All other keys are ignoring. If some parameter absent on current LVE version (for example pmem on LVE4), it will be ignored too. :param reseller_name: Reseller name """ lvectl_args = [command, str(container_id)] # 1. Check arguments and form lvectl call arguments if "mep" in limits or "ep" in limits: limits["maxEntryProcs"] = limits.get("mep", limits.get("ep")) for k in ("speed", "vmem", "pmem", "maxEntryProcs", "io", "nproc", "iops"): v = limits.get(k) if v is None: continue v = str(v).strip() if k in ["pmem", "nproc", "iops"] and self.lve_version == 4: continue if k in ["iops"] and self.lve_version == 6: continue if k == "speed" and v.isdigit(): v = f"{v}%" lvectl_args.append(f"--{k}={v}") if len(lvectl_args) <= 2: return 0 if limits.get("save-all-parameters"): lvectl_args.append("--save-all-parameters") # Add reseller name if need if reseller_name: lvectl_args.append(f"--reseller={reseller_name}") # 2. call utility to set limits ret_code, out, err = exec_utility(self._UTILITY_PATH, lvectl_args, stderr=True) if ret_code != 0: # Set limits error raise LvectlException(self.__set_error('Limits', container_id, err)) def _set_container_limits_by_id_or_name(self, reseller_id): """ Set limits for given container id :param reseller_id: LVP id or reseller's name or '--all' :type reseller_id: int | str """ lvectl_args = ['set-reseller', reseller_id] ret_code, out, err = exec_utility(self._UTILITY_PATH, lvectl_args, stderr=True) if ret_code != 0: # Set limits error raise LvectlException(self.__set_error('Limits', reseller_id, err)) def set_lve_limits_by_user_id(self, lve_id, limits, reseller_name=None): """ Wrapper for _set_container_limits_by_id, set limits for lve_id; :param int lve_id: user's container id :param limits: dict with limits to set :param reseller_name: Reseller name :return: int """ if bool(lve_id) and not self.get_package_name_by_user_id(lve_id): return 1 self._set_container_limits_by_id('set', lve_id, limits, reseller_name=reseller_name) return 0 def set_lvp_limits_by_reseller_id(self, lvp_id, limits): """ Wrapper for _set_container_limits_by_id, set limits for lvp_id; :type lvp_id: int :type limits: dict :return: int """ self._set_container_limits_by_id('set-reseller', lvp_id, limits) return 0 def set_lvp_defaults_by_reseller_id(self, lvp_id, limits): """ Wrapper for _set_container_limits_by_id, set limits for lvp_id; :type lvp_id: int :type limits: dict :return: int """ self._set_container_limits_by_id('set-reseller-default', lvp_id, limits) return 0 def set_lve_unlimited(self, lve_id, reseller_name=None): """ Set unlimited LVE for lve_id :param: lve_id `int`: LVE id :param reseller_name: Reseller name :return: 0 """ args = ["set", str(lve_id), "--unlimited"] if reseller_name is not None: args.extend(['--reseller', reseller_name]) ret_code, err = exec_utility(self._UTILITY_PATH, args) if ret_code != 0: raise LvectlException(self.__set_error('Unlimited', lve_id, err)) return 0 def set_lvp_unlimited(self, lvp_id): """ Set unlimited LVP for reseller; Accepts name or id; :type lvp_id: str | int :return: 0 """ args = ["set-reseller", str(lvp_id), "--unlimited"] ret_code, err = exec_utility(self._UTILITY_PATH, args) if ret_code != 0: raise LvectlException(self.__set_error('Unlimited', lvp_id, err)) return 0 def reset_lve_limits(self, lve_id, limits): """ Reset LVE limits for lve_id. Set default limits for LVE package or system default LVE :param: lve_id `int: LVE id :return: 0 """ args = ["set", str(lve_id), f"--default={','.join(limits)}"] ret_code, err = exec_utility(self._UTILITY_PATH, args) if ret_code != 0: raise LvectlException(self.__set_error('Default', lve_id, err)) return 0 def reset_reseller_limits(self, reseller_name, limits): """ Reset LVP limits for reseller_name. :param: reseller_name str: :return: 0 """ args = ["set-reseller", str(reseller_name), f"--default={','.join(limits)}"] ret_code, err = exec_utility(self._UTILITY_PATH, args) if ret_code != 0: raise LvectlException(self.__set_error('Default', reseller_name, err)) return 0 def apply_all_limits(self): """ Apply all already configured limits :return: ret code """ ret_code, err = exec_utility(self._UTILITY_PATH, ["apply", "all"]) return ret_code def disable_reseller_limits(self, reseller_name): """ Disable reseller limits for given name; Equivalent to lvectl remove-reseller <str> :type reseller_name: str :return: 0 """ args = ["remove-reseller", str(reseller_name), '--json'] ret_code, err = exec_utility(self._UTILITY_PATH, args) if ret_code != 0: raise LvectlException(self.__set_error('Disable reseller limits', reseller_name, err)) return 0 def _load_resellers_info(self, human_readable_numbers, reseller_name): """ Load information about resellers; :type human_readable_numbers: bool :type reseller_name: str | None :return: """ self._resellers_data = {} if not lvectllib.lve.reseller_limit_supported(): return lvectl_args = ['list-reseller', '--json', '--with-name'] if not human_readable_numbers: lvectl_args.append('--bytes') ret_code, std_out = exec_utility(self._UTILITY_PATH, lvectl_args) loaded_json = json.loads(std_out) if ret_code != 0: raise LvectlException(loaded_json['msg']) # To edit reseller limits in UI, admin should have ability to see # all resellers, even those for which limits have not yet been enabled. # So we just init dict with all resellers and frontend will rely # on empty "limits" key to determine whether reseller limits has been # already set for particular reseller or not. for reseller in cpapi.resellers(): if reseller_name and reseller_name != reseller: continue self._resellers_data[reseller] = {'name': reseller, 'limits': {}} # Refresh settings with utility's data for reseller in loaded_json.get('data', []): id_, name = reseller['ID'].split(':') if reseller_name and reseller_name != name: continue reseller_info = { 'id': id_, 'name': name, 'limits': { 'cpu': {'all': reseller['SPEED']}, 'ep': reseller['EP'], 'io': {'all': reseller['IO']}, 'iops': reseller['IOPS'], 'nproc': reseller['NPROC'], 'pmem': _convert_memory_value_to_adaptive_format(reseller['PMEM'], human_readable_numbers), } } self._resellers_data[reseller_info['name']] = reseller_info def _load_info(self, human_readable_numbers, userid=None, reseller=None): """ Loads all package info from lvectl :param human_readable_numbers: if True PMEM and VMEM limits in KBytes, MBytes or GBytes False - in bytes :return: None """ if self._resellers_data is None: self._load_resellers_info(human_readable_numbers, reseller) if self._package_data is not None: return # Get panel packages data from lvectl utility # Create Panel packages data self._package_data = {} if userid is not None: lvectl_args = ['paneluserlimits', str(userid)] elif reseller is not None: lvectl_args = ['paneluserslimits', str(reseller)] else: lvectl_args = ['paneluserslimits'] lvectl_args.append('--json') if not human_readable_numbers: lvectl_args.append('--bytes') ret_code, std_out = exec_utility(self._UTILITY_PATH, lvectl_args) loaded_json = json.loads(std_out) if ret_code != 0: raise LvectlException(loaded_json['msg']) json_data = loaded_json['data'] for pkg_data in json_data: pkg_limits = {} pkg_name = pkg_data['PACKAGE'] if pkg_name == 'VE_DEFAULT': pkg_name = 'DEFAULT' # self._package_data[pkg_data['ID']] = pkg_name pkg_limits['PACKAGE'] = pkg_name # All LVE pkg_limits['cpu'] = {'all': pkg_data['SPEED']} pkg_limits['io'] = {'all': pkg_data['IO']} pkg_limits['vmem'] = _convert_memory_value_to_adaptive_format(pkg_data['VMEM'], human_readable_numbers) pkg_limits['ep'] = pkg_data['EP'] if self.lve_version >= 6: # LVE 6, 8 pkg_limits['pmem'] = _convert_memory_value_to_adaptive_format(pkg_data['PMEM'], human_readable_numbers) pkg_limits['nproc'] = pkg_data['NPROC'] if self.lve_version >= 8: # LVE 8 pkg_limits['iops'] = pkg_data['IOPS'] self._package_data[int(pkg_data['ID'])] = pkg_limits if reseller is not None: reseller_defaults = self.get_default_limits_by_reseller_name(reseller, human_readable_numbers) if reseller_defaults: self._package_data[0] = reseller_defaults return if 0 not in self._package_data: # Defaults limits not found, set them manually pkg_limits = {} pkg_limits['PACKAGE'] = 'DEFAULT' # All LVE pkg_limits['cpu'] = {'all': '0'} pkg_limits['io'] = {'all': '0'} pkg_limits['vmem'] = '0K' pkg_limits['ep'] = '0' if self.lve_version >= 6: # LVE 6, 8 pkg_limits['pmem'] = '0K' pkg_limits['nproc'] = '0' if self.lve_version >= 8: # LVE 8 pkg_limits['iops'] = '0' self._package_data[0] = pkg_limits