D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
alt
/
python37
/
lib
/
python3.7
/
site-packages
/
clwpos
/
object_cache
/
Filename :
redis_utils.py
back
Copy
# -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT from __future__ import absolute_import import os import re import subprocess from functools import lru_cache from pathlib import Path from typing import List from pkg_resources import parse_version from secureio import write_file_via_tempfile from clcommon.cpapi import getCPName, CPANEL_NAME, PLESK_NAME from clwpos.constants import ( RedisRequiredConstants, EA_PHP_PREFIX, PLESK_PHP_PREFIX, CAGEFSCTL ) from clwpos.data_collector_utils import get_cached_php_installed_versions from clwpos.logsetup import setup_logging from clwpos.utils import ( daemon_communicate, PHP, run_in_cagefs_if_needed, create_pid_file, acquire_lock ) _logger = setup_logging(__name__) BASE_CPANEL_EA_PHP_DIR = '/opt/cpanel' BASE_PLESK_PHP_DIR = '/opt/plesk/php' def configurator(): """Instantiate appropriate configurator""" panel = getCPName() if panel == CPANEL_NAME: return EaPhpRedisConfigurator() elif panel == PLESK_NAME: return PleskPhpRedisConfigurator() raise Exception("No PHP Redis configurator currently found") class RedisConfigurator: def configure(self): with acquire_lock(os.path.join('/var/run', self.PHP_PREFIX), attempts=1): self.configure_redis_extension() def configure_redis_extension(self): """ Sets up redis if needed: - installing package - enables in .ini file """ need_cagefs_update = False wait_child_process = bool(os.environ.get('CL_WPOS_WAIT_CHILD_PROCESS')) php_versions_redis_data = { php: _redis_extension_info(PHP(php)) for php in self.get_supported_php() } php_versions_to_enable_redis = [ php for php, redis_data in php_versions_redis_data.items() if not redis_data.get('is_present') or not redis_data.get('is_loaded') ] if not php_versions_to_enable_redis: return with create_pid_file(self.PHP_PREFIX): for php in php_versions_to_enable_redis: redis_data = php_versions_redis_data.get(php) if not redis_data.get('is_present'): redis_package = self.redis_package(php) result = subprocess.run( ['yum', '-y', 'install', *self._additional_repos, redis_package], capture_output=True, text=True) if result.returncode != 0 and 'Nothing to do' not in result.stdout: _logger.error( 'Failed to install package %s, due to reason: %s', redis_package, f'{result.stdout}\n{result.stderr}') continue self.enable_redis_extension(php) need_cagefs_update = True elif not redis_data.get('is_loaded'): self.enable_redis_extension(php) need_cagefs_update = True if need_cagefs_update and wait_child_process and os.path.isfile( CAGEFSCTL): try: subprocess.run([CAGEFSCTL, '--check-cagefs-initialized'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) except subprocess.CalledProcessError: _logger.info( 'CageFS in unintialized, skipping force-update') else: subprocess.run( [CAGEFSCTL, '--wait-lock', '--force-update'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def enable_redis_extension(self, php_version): """ Enables (if needed) redis extension in .ini config """ path = self.redis_ini(php_version) keyword = 'redis.so' if not os.path.exists(path): _logger.error( 'Redis extension config: %s is not found, ensure corresponding rpm package installed: %s', str(path), self.redis_package(php_version)) return with open(path) as f: extension_data = f.readlines() uncommented_pattern = re.compile(fr'^\s*extension\s*=\s*{keyword}') commented_pattern = re.compile(fr'^\s*;\s*extension\s*=\s*{keyword}') enabled_line = f'extension = {keyword}\n' was_enabled = False lines = [] for line in extension_data: if uncommented_pattern.match(line): return if not was_enabled and commented_pattern.match(line): lines.append(enabled_line) was_enabled = True else: lines.append(line) if not was_enabled: lines.append(enabled_line) write_file_via_tempfile(''.join(lines), path, 0o644) @property def _additional_repos(self): return tuple() @property def PHP_PREFIX(self): raise NotImplementedError def get_supported_php(self): """""" raise NotImplementedError def redis_package(self, php): raise NotImplementedError def redis_ini(self, php_version): raise NotImplementedError class EaPhpRedisConfigurator(RedisConfigurator): """ Install and configure redis extensions for cPanel ea-php """ @property def PHP_PREFIX(self): return EA_PHP_PREFIX def get_supported_php(self): """ Looks through /opt/cpanel and gets installed phps """ base_dir = Path(BASE_CPANEL_EA_PHP_DIR) minimal_supported = parse_version('ea-php74') supported = [] for item in os.listdir(base_dir): if item.startswith('ea-php') and PHP( item).bin().exists() and parse_version( item) >= minimal_supported: supported.append(item) return supported def redis_package(self, php): return f'{php}-php-redis' def redis_ini(self, php_version): return Path(PHP(php_version).dir()).joinpath('root/etc/php.d/50-redis.ini') class PleskPhpRedisConfigurator(RedisConfigurator): """ Install and configure redis extensions for Plesk php """ @property def _additional_repos(self): return '--enablerepo', 'PLESK*' @property def PHP_PREFIX(self): return PLESK_PHP_PREFIX def get_supported_php(self): """ Looks through /opt/plesk/php and gets installed phps. /opt/plesk/php contains plain version directories, e.g. 7.4; 8.0; 8.1 """ base_dir = Path(BASE_PLESK_PHP_DIR) minimal_supported = parse_version('plesk-php74') supported = [] for item in os.listdir(base_dir): _php = f"plesk-php{item.replace('.', '')}" if PHP(_php).bin().exists() and parse_version( _php) >= minimal_supported: supported.append(_php) return supported def redis_package(self, php): return f'{php}-redis' def redis_ini(self, php_version): return Path(PHP(php_version).dir()).joinpath(f'etc/php.d/redis.ini') @lru_cache() def _redis_extension_info(version: PHP) -> dict: is_present = bool(list(version.modules_dir().glob("**/redis.so"))) php_bin_path = version.bin() if os.geteuid() == 0: exec_func = subprocess.run else: exec_func = run_in_cagefs_if_needed is_loaded = exec_func( f'{php_bin_path} -m | /bin/grep redis', shell=True, executable='/bin/bash', env={} ).returncode == 0 if is_present else False return { "is_present": is_present, "is_loaded": is_loaded } def filter_php_versions_with_not_loaded_redis(php_versions: List[PHP]) -> List[PHP]: """ Filter list of given php versions to find out for which redis extension is presented but not loaded. """ php_versions_with_not_loaded_redis = [] for version in php_versions: php_redis_info = _redis_extension_info(version) if not php_redis_info['is_loaded'] and php_redis_info['is_present']: php_versions_with_not_loaded_redis.append(version) return php_versions_with_not_loaded_redis @lru_cache(maxsize=None) def get_cached_php_versions_with_redis_loaded() -> set: """ List all installed php version on the system which has redis-extension enabled :return: installed php versions which has redis-extension """ versions = get_cached_php_installed_versions() return {version for version in versions if _redis_extension_info(version)["is_loaded"]} @lru_cache(maxsize=None) def get_cached_php_versions_with_redis_present() -> set: """ List all installed php version on the system which has redis-extension installed :return: installed php versions which has redis-extension installed """ versions = get_cached_php_installed_versions() return {version for version in versions if _redis_extension_info(version)["is_present"]} def reload_redis(uid: int = None, force: str = 'no'): """ Make redis reload via CLWPOS daemon :param uid: User uid (optional) :param force: force reload w/o config check """ cmd_dict = {"command": "reload", 'force_reload': force} if uid: cmd_dict['uid'] = uid daemon_communicate(cmd_dict)