/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
clwpos
/
object_cache
/
Upload Filee
HOME
# -*- 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 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, DIRECTADMIN_NAME from clwpos.constants import ( RedisRequiredConstants, EA_PHP_PREFIX, PLESK_PHP_PREFIX, DIRECTADMIN_PREFIX, CAGEFSCTL ) from clwpos.data_collector_utils import get_cached_php_installed_versions from clwpos.php.base import PHP from clwpos.logsetup import setup_logging from clwpos.utils import ( daemon_communicate, 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() elif panel == DIRECTADMIN_NAME: return DirectAdminPhpRedisConfigurator() 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 _update_cagefs(self, need_cagefs_update, wait_child_process): 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 uninitialized, skipping force-update') else: subprocess.run( [CAGEFSCTL, '--wait-lock', '--force-update'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 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_to_enable_redis = [] for php in self.get_supported_php(): if php.is_extension_loaded('redis'): _logger.info('Redis extension is already installed and configured for %s', php.identifier) continue php_versions_to_enable_redis.append(php) if not php_versions_to_enable_redis: _logger.info('All ea-php versions have redis installed and active') return with create_pid_file(self.PHP_PREFIX): for php in php_versions_to_enable_redis: if not php.is_extension_installed('redis'): redis_package = self.redis_package(php) _logger.info('Trying to install %s package', redis_package) 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 _logger.info('Package successfully installed, activating it') self.enable_redis_extension(php) need_cagefs_update = True elif not php.is_extension_loaded('redis'): self.enable_redis_extension(php) need_cagefs_update = True self._update_cagefs(need_cagefs_update, wait_child_process) 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) -> List[PHP]: """""" raise NotImplementedError def redis_package(self, php: PHP) -> str: raise NotImplementedError def redis_ini(self, php_version: PHP) -> Path: 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) -> List[PHP]: """ Looks through /opt/cpanel and gets installed phps """ php_versions = get_cached_php_installed_versions() minimal_supported = parse_version('74') supported = [] for php_description in php_versions: if php_description.identifier.startswith('ea-php') \ and os.path.exists(php_description.bin) \ and parse_version(php_description.identifier.replace('ea-php', '')) >= minimal_supported: supported.append(php_description) return supported def redis_package(self, php): return f'{php.identifier}-php-redis' def redis_ini(self, php_version: PHP) -> Path: return Path(php_version.dir).joinpath('etc/php.d/50-redis.ini') class DirectAdminPhpRedisConfigurator(RedisConfigurator): """ Installs and configure redis extensions for DirectAdmin php NOTE: directadmin enables redis for all compiled versions or for none https://docs.directadmin.com/webservices/php/php-extensions.html#installing-extensions """ @property def PHP_PREFIX(self): return DIRECTADMIN_PREFIX def is_redis_already_enabled(self): """ If at least for 1 supported version redis is not loaded -> False """ supported_versions = self.get_supported_php() for version_item in supported_versions: if not version_item.is_extension_loaded('redis'): return False return True def configure_redis_extension(self): wait_child_process = bool(os.environ.get('CL_WPOS_WAIT_CHILD_PROCESS')) with create_pid_file(self.PHP_PREFIX): try: if not self.is_redis_already_enabled(): subprocess.run(['/usr/local/directadmin/custombuild/build', 'set_php', 'redis', 'yes'], capture_output=True, text=True) subprocess.run(['/usr/local/directadmin/custombuild/build', 'php_redis'], capture_output=True, text=True) self._update_cagefs(need_cagefs_update=True, wait_child_process=wait_child_process) except Exception: _logger.exception('Error on configuring redis extension for DirectAdmin') def get_supported_php(self) -> List[PHP]: php_versions = get_cached_php_installed_versions() minimal_supported = parse_version('74') supported = [] for php_description in php_versions: if (php_description.identifier.startswith(DIRECTADMIN_PREFIX) and parse_version(php_description.version.replace('.', '')) >= minimal_supported): supported.append(php_description) return supported 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) -> List[PHP]: """ Looks through /opt/plesk/php and gets installed phps. /opt/plesk/php contains plain version directories, e.g. 7.4; 8.0; 8.1 """ php_versions = get_cached_php_installed_versions() minimal_supported = parse_version('74') supported = [] for php_description in php_versions: if php_description.identifier.startswith('plesk-php') \ and os.path.exists(php_description.bin) \ and parse_version(php_description.identifier.replace('plesk-php', '')) >= minimal_supported: supported.append(php_description) return supported def redis_package(self, php): return f'{php.identifier}-redis' def redis_ini(self, php_version): return Path(php_version.dir).joinpath(f'etc/php.d/redis.ini') 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: if not version.is_extension_loaded('redis') and version.is_extension_installed('redis'): php_versions_with_not_loaded_redis.append(version) return php_versions_with_not_loaded_redis def reload_redis(uid: int = None, force: str = 'no', skip_last_reload_time: str = 'no'): """ Make redis reload via CLWPOS daemon :param uid: User uid (optional) :param force: force reload w/o config check :param skip_last_reload_time: skip check of last redis reload for user """ cmd_dict = {"command": "reload", 'force_reload': force, 'skip_last_reload_time': skip_last_reload_time} if uid: cmd_dict['uid'] = uid daemon_communicate(cmd_dict)