晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。 林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。 见渔人,乃大惊,问所从来。具答之。便要还家,设酒杀鸡作食。村中闻有此人,咸来问讯。自云先世避秦时乱,率妻子邑人来此绝境,不复出焉,遂与外人间隔。问今是何世,乃不知有汉,无论魏晋。此人一一为具言所闻,皆叹惋。余人各复延至其家,皆出酒食。停数日,辞去。此中人语云:“不足为外人道也。”(间隔 一作:隔绝) 既出,得其船,便扶向路,处处志之。及郡下,诣太守,说如此。太守即遣人随其往,寻向所志,遂迷,不复得路。 南阳刘子骥,高尚士也,闻之,欣然规往。未果,寻病终。后遂无问津者。
| DIR:/opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/plugins/generic/burster/ |
| Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/plugins/generic/burster/config.py |
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2023 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
import typing
import dataclasses
from pathlib import Path
from logging import Logger
from datetime import timedelta
from dataclasses import dataclass
from collections import ChainMap
from typing import Any, Generator, TypedDict, Mapping, Self, NamedTuple, Iterable, NotRequired
import sqlalchemy as sa
from ._logs import logger
# TODO(vlebedev): Extract from package metadata instead of hardcoding?
CONFIG_FILE = Path('/etc/sysconfig/lvestats.config/LveLimitsBurster.cfg')
FEATURE_FLAG_FILE = Path('/opt/cloudlinux/flags/enabled-flags.d/burstable-limits.flag')
class PluginConfig(TypedDict):
bursting_enabled: NotRequired[str]
server_id: NotRequired[str]
bursting_quota_sec: str
bursting_quota_window_sec: str
bursting_idle_time_threshold: str
bursting_cpu_multiplier: str
bursting_io_multiplier: str
bursting_database_dump_period_sec: str
bursting_idle_time_samples_num: str
bursting_debug_mode: NotRequired[str]
_all_burster_plugin_config_keys = frozenset(PluginConfig.__annotations__.keys())
@dataclass(frozen=True)
class Config:
server_id: str
bursting_quota: timedelta
bursting_quota_window: timedelta
bursting_cpu_multiplier: float
bursting_io_multiplier: float
idle_time_threshold: float
db_dump_period: timedelta
idle_time_samples: int
fail_fast: bool = True
def __post_init__(self) -> None:
if self.bursting_quota > self.bursting_quota_window:
raise ValueError('Bursting quota must be less than or equal to bursting quota window!')
_all_config_keys = frozenset(f.name for f in dataclasses.fields(Config))
def is_bursting_enabled(config_file=CONFIG_FILE) -> bool:
try:
raw_config = read_raw_config(config_file)
except FileNotFoundError:
return False
raw_key = 'bursting_enabled'
assert raw_key in _all_burster_plugin_config_keys
try:
raw_value = raw_config[raw_key]
except KeyError:
return False
try:
return get_boolean(raw_value)
except ValueError:
return False
def is_bursting_supported(feature_flag_file: Path = FEATURE_FLAG_FILE) -> bool:
# NOTE(vlebedev): These imports requires some shared library to be present in order to succeed,
# so deffer it until it's really needed to make unittests writing/running easier.
from clcommon.utils import get_cl_version, is_ubuntu # pylint: disable=import-outside-toplevel
from clcommon.cpapi import Feature, is_panel_feature_supported # pylint: disable=import-outside-toplevel
if not is_panel_feature_supported(Feature.LVE):
return False
if is_ubuntu():
return False
cl_version = get_cl_version()
if cl_version is None:
return False
try:
if int(cl_version.removeprefix('cl').removesuffix('h')) < 8:
return False
except ValueError:
return False
return feature_flag_file.exists()
def _identity(raw_value: str) -> str:
return raw_value
def get_boolean(raw_value: str) -> bool:
value = raw_value.lower()
if value not in {'true', 'false'}:
raise ValueError(f'Unexpected value: {value}')
return value == 'true'
def _get_timedelta_from_seconds(raw_value: str) -> timedelta:
seconds = int(raw_value)
return timedelta(seconds=seconds)
_raw_key_to_spec = {
'bursting_enabled': ('enabled', get_boolean),
'server_id': ('server_id', _identity),
'bursting_debug_mode': ('fail_fast', get_boolean),
'bursting_quota_sec': ('bursting_quota', _get_timedelta_from_seconds),
'bursting_quota_window_sec': ('bursting_quota_window', _get_timedelta_from_seconds),
'bursting_cpu_multiplier': ('bursting_cpu_multiplier', float),
'bursting_io_multiplier': ('bursting_io_multiplier', float),
'bursting_idle_time_threshold': ('idle_time_threshold', float),
'bursting_database_dump_period_sec': ('db_dump_period', _get_timedelta_from_seconds),
'bursting_idle_time_samples_num': ('idle_time_samples', int),
}
_config_to_raw_key = {v[0]: k for k, v in _raw_key_to_spec.items()}
assert _raw_key_to_spec.keys() == _all_burster_plugin_config_keys
assert {k for k, _ in _raw_key_to_spec.values()}.issuperset(_all_config_keys)
def _process_raw_config(raw_config: Mapping[str, str]) -> dict[str, Any]:
cfg_key_to_parsed_value, errors_by_cfg_key = {}, {}
for config_key, raw_value in raw_config.items():
try:
_, extractor = _raw_key_to_spec[config_key]
except KeyError:
# NOTE(vlebedev): Currently config dict contains all the keys from _all_ .cfg files parsed by
# lvestats. So there is no point as report fields not present in `Confg` typing
# as "unknown" or something like that - they might well belong to some other plugin =/
# errors_by_cfg_key[config_key] = f'Unknown config key'
continue
try:
value = extractor(raw_value)
except ValueError as e:
errors_by_cfg_key[config_key] = str(e)
continue
cfg_key_to_parsed_value[config_key] = value
if len(errors_by_cfg_key) > 0:
logger.warning(
"Failed to parse some config keys: \n%s",
"\n".join(f"* {k}: {e}" for k, e in errors_by_cfg_key.items()),
)
result = {_raw_key_to_spec[k][0]: v for k, v in cfg_key_to_parsed_value.items()}
return result
class MissingKeysInRawConfig(ValueError):
def __init__(self, missing_raw_keys: Iterable[str]) -> None:
missing_raw_keys = frozenset(missing_raw_keys)
msg = "Missing config keys: " + ", ".join(missing_raw_keys) + "!"
super().__init__(msg, missing_raw_keys)
@property
def missing_raw_keys(self) -> frozenset[str]:
return typing.cast(frozenset[str], self.args[1])
class ConfigUpdate(NamedTuple):
@classmethod
def from_plugin_config(cls, config: PluginConfig) -> Self:
assert all(isinstance(v, str) for v in config.values())
external_params = _process_raw_config(typing.cast(Mapping[str, str], config))
default_params = {
'enabled': False,
'server_id': 'localhost',
'fail_fast': False,
}
if (defaults_used := default_params.keys() - external_params.keys()):
logger.info('Using default values for: %s', defaults_used)
params = ChainMap(external_params, default_params)
missing_config_keys = _all_config_keys - params.keys()
if missing_config_keys:
raise MissingKeysInRawConfig(_config_to_raw_key[k] for k in missing_config_keys)
return cls(
enabled=params['enabled'],
config=Config(**{k: params[k] for k in _all_config_keys})
)
enabled: bool
config: Config
class StartupParams(NamedTuple):
@classmethod
def wait(cls) -> Generator[None, ConfigUpdate | sa.engine.Engine, Self]:
required_keys = frozenset(cls._fields)
result = {}
enabled = False
while enabled is False or result.keys() != required_keys:
match (yield):
case sa.engine.Engine() as engine:
result['engine'] = engine
case ConfigUpdate(enabled=enabled, config=config):
result['config'] = config
return cls(**result)
engine: sa.engine.Engine
config: Config
def read_raw_config(file: Path = CONFIG_FILE, _logger: Logger = logger) -> Mapping[str, str]:
result = {}
for line in file.read_text(encoding='utf-8').splitlines():
try:
key, value = line.split('=', maxsplit=1)
except ValueError:
_logger.warning('Failed to parse config line: %s', line)
continue
if key in result:
_logger.warning('Duplicate key %s - latest value will be used', key)
result[key] = value
return result
|