Добавляю уже существующий проект в репозиторий GIT

This commit is contained in:
2026-02-09 20:45:47 +03:00
commit 5bbb585d9f
11 changed files with 2198 additions and 0 deletions

400
c1_cluster.py Normal file
View File

@@ -0,0 +1,400 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Модуль для работы с кластером 1С через SSH
"""
import logging
from typing import Optional, List, Dict
logger = logging.getLogger(__name__)
class C1ClusterOperations:
"""
Класс для операций с кластером 1С через SSH
"""
def __init__(self, ssh_client, srv_1c: str = "", c1_claster_user: str = "", c1_claster_pass: str = ""):
"""
Инициализация модуля 1С кластера
Args:
ssh_client: Экземпляр SSHBase для выполнения команд
srv_1c: Имя LXC контейнера с 1С
c1_claster_user: Пользователь кластера 1С
c1_claster_pass: Пароль кластера 1С
"""
self.ssh = ssh_client
self.srv_1c = srv_1c
self.c1_claster_user = c1_claster_user
self.c1_claster_pass = c1_claster_pass
def set_srv_1c(self, srv_1c: str) -> None:
"""
Установка имени LXC контейнера с 1С
Args:
srv_1c: Имя LXC контейнера с 1С
"""
if not srv_1c or not isinstance(srv_1c, str) or not srv_1c.strip():
raise ValueError("srv_1c должен быть непустой строкой")
self.srv_1c = srv_1c
logger.info(f"Установлено имя контейнера 1С: {srv_1c}")
def set_cluster_credentials(self, c1_claster_user: str, c1_claster_pass: str) -> None:
"""
Установка учетных данных кластера 1С
Args:
c1_claster_user: Пользователь кластера 1С
c1_claster_pass: Пароль кластера 1С
"""
if not c1_claster_user or not isinstance(c1_claster_user, str) or not c1_claster_user.strip():
raise ValueError("c1_claster_user должен быть непустой строкой")
if not c1_claster_pass or not isinstance(c1_claster_pass, str) or not c1_claster_pass.strip():
raise ValueError("c1_claster_pass должен быть непустой строкой")
self.c1_claster_user = c1_claster_user
self.c1_claster_pass = c1_claster_pass
logger.info(f"Установлены учетные данные кластера 1С для пользователя: {c1_claster_user}")
def set_cluster_user(self, c1_claster_user: str) -> None:
"""
Установка пользователя кластера 1С
Args:
c1_claster_user: Пользователь кластера 1С
"""
if not c1_claster_user or not isinstance(c1_claster_user, str) or not c1_claster_user.strip():
raise ValueError("c1_claster_user должен быть непустой строкой")
self.c1_claster_user = c1_claster_user
logger.info(f"Установлен пользователь кластера 1С: {c1_claster_user}")
def set_cluster_password(self, c1_claster_pass: str) -> None:
"""
Установка пароля кластера 1С
Args:
c1_claster_pass: Пароль кластера 1С
"""
if not c1_claster_pass or not isinstance(c1_claster_pass, str) or not c1_claster_pass.strip():
raise ValueError("c1_claster_pass должен быть непустой строкой")
self.c1_claster_pass = c1_claster_pass
logger.info("Пароль кластера 1С обновлен")
def _validate_config(self) -> None:
"""
Проверка наличия необходимых конфигурационных параметров
Raises:
ValueError: Если не установлены необходимые параметры
"""
if not self.srv_1c or not self.srv_1c.strip():
raise ValueError("srv_1c не установлен. Используйте set_srv_1c() или передайте при инициализации")
if not self.c1_claster_user or not self.c1_claster_user.strip():
raise ValueError("c1_claster_user не установлен. Используйте set_cluster_user() или передайте при инициализации")
if not self.c1_claster_pass or not self.c1_claster_pass.strip():
raise ValueError("c1_claster_pass не установлен. Используйте set_cluster_password() или передайте при инициализации")
def cluster_version(self) -> str:
"""
Получение версии кластера 1С
Returns:
str: Версия кластера
"""
self._validate_config()
try:
logger.info(f"Получение версии кластера 1С из контейнера {self.srv_1c}")
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- ls -lh /opt/1cv8/x86_64/ | grep 8.3')
if err and err.strip():
logger.error(f"Ошибка получения версии кластера: {err}")
raise Exception(f"Ошибка получения версии кластера: {err}")
version = std.split(' ')[-1].strip()
if not version:
logger.error("Не удалось определить версию кластера")
raise Exception("Не удалось определить версию кластера")
logger.info(f"Версия кластера 1С: {version}")
return version
except Exception as e:
logger.error(f"Ошибка при получении версии кластера: {e}")
raise Exception(f"Ошибка при получении версии кластера: {e}")
def cluster_daemon_start(self) -> str:
"""
Запуск демона кластера 1С
Returns:
str: Вывод ошибок (если есть)
"""
self._validate_config()
try:
logger.info(f"Запуск демона кластера 1С в контейнере {self.srv_1c}")
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/ras --daemon cluster')
if err and err.strip():
logger.warning(f"Предупреждение при запуске демона: {err}")
else:
logger.info("Демон кластера 1С запущен успешно")
return err
except Exception as e:
logger.error(f"Ошибка при запуске демона кластера: {e}")
raise Exception(f"Ошибка при запуске демона кластера: {e}")
def cluster_id(self) -> str:
"""
Получение ID кластера 1С
Returns:
str: ID кластера
"""
self._validate_config()
try:
logger.info(f"Получение ID кластера 1С из контейнера {self.srv_1c}")
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac cluster list')
if err and err.strip():
logger.error(f"Ошибка получения ID кластера: {err}")
raise Exception(f"Ошибка получения ID кластера: {err}")
if not std or not std.strip():
logger.error("Пустой ответ при получении ID кластера")
raise Exception("Пустой ответ при получении ID кластера")
cluster_id = std.split('\n')[0].split(':')[1].strip()
if not cluster_id:
logger.error("Не удалось извлечь ID кластера")
raise Exception("Не удалось извлечь ID кластера")
logger.info(f"ID кластера 1С: {cluster_id}")
return cluster_id
except Exception as e:
logger.error(f"Ошибка при получении ID кластера: {e}")
raise Exception(f"Ошибка при получении ID кластера: {e}")
def base_list(self) -> List[Dict[str, List[str]]]:
"""
Получение списка баз данных 1С
Returns:
list[dict]: Список словарей с информацией о базах данных
"""
self._validate_config()
try:
logger.info(f"Получение списка баз данных 1С из контейнера {self.srv_1c}")
std, err = self.ssh.cmd(
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
f"infobase summary list --cluster={self.cluster_id()} "
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}'"
)
if err and err.strip():
logger.error(f"Ошибка получения списка баз 1С: {err}")
raise Exception(f"Ошибка получения списка баз 1С: {err}")
list_of_base_info = []
# Разделяем по двойным переводам строк (каждая база отделена двумя \n)
for raw_info in std.split('\n\n')[:-1]:
if not raw_info.strip():
continue
# Исправлено: raw_info уже разделен, не нужно снова split('\n\n')
lines = raw_info.split('\n')
if len(lines) < 3:
continue
try:
bases_info = {
"id": lines[0].split(' : ')[1].split() if ' : ' in lines[0] else [],
"name": lines[1].split(' : ')[1].split() if ' : ' in lines[1] else [],
"description": lines[2].split(' : ')[1].split() if ' : ' in lines[2] else []
}
list_of_base_info.append(bases_info)
except (IndexError, ValueError) as e:
# Пропускаем некорректно отформатированные записи
continue
logger.info(f"Найдено {len(list_of_base_info)} баз данных 1С")
return list_of_base_info
except Exception as e:
logger.error(f"Ошибка при получении списка баз 1С: {e}")
raise Exception(f"Ошибка при получении списка баз 1С: {e}")
def base_id(self, base_name: str) -> Optional[str]:
"""
Получает ID базы данных 1С по её имени
Args:
base_name: Имя базы данных
Returns:
str: ID базы данных или None, если база не найдена
"""
self._validate_config()
if not base_name or not isinstance(base_name, str) or not base_name.strip():
raise ValueError("base_name должен быть непустой строкой")
try:
logger.debug(f"Поиск ID базы данных {base_name} в контейнере {self.srv_1c}")
for base in self.base_list():
if base.get('name') and len(base['name']) > 0 and base['name'][0] == base_name:
if base.get('id') and len(base['id']) > 0:
base_id = base['id'][0]
logger.info(f"Найден ID базы данных {base_name}: {base_id}")
return base_id
logger.warning(f"База данных {base_name} не найдена")
return None
except Exception as e:
logger.error(f"Ошибка при получении ID базы данных: {e}")
raise Exception(f"Ошибка при получении ID базы данных: {e}")
def base_info(self, base_name: str, infobase_user: str, infobase_password: str) -> Optional[Dict[str, str]]:
"""
Получение информации о базе данных 1С
Args:
base_name: Имя базы данных
infobase_user: Пользователь информационной базы
infobase_password: Пароль пользователя информационной базы
Returns:
dict: Словарь с информацией о базе данных или None, если база не найдена
"""
self._validate_config()
# Валидация входных данных
for param_name, param_value in [
('base_name', base_name),
('infobase_user', infobase_user),
('infobase_password', infobase_password)
]:
if not param_value or not isinstance(param_value, str) or not param_value.strip():
raise ValueError(f"{param_name} должен быть непустой строкой")
try:
base_id = self.base_id(base_name)
if not base_id:
logger.warning(f"База данных {base_name} не найдена")
return None
logger.info(f"Получение информации о базе данных {base_name} (ID: {base_id})")
std, err = self.ssh.cmd(
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
f"infobase info --cluster={self.cluster_id()} "
f"--infobase={base_id} "
f"--infobase-user={infobase_user} --infobase-pwd='{infobase_password}' "
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}'"
)
if err and err.strip():
logger.error(f"Ошибка получения информации о базе данных: {err}")
raise Exception(f"Ошибка получения информации о базе данных: {err}")
result = {}
# Разделяем входную строку по переводам строк
lines = std.strip().split('\n')
for line in lines:
# Пропускаем пустые строки
if not line.strip():
continue
# Разделяем строку на имя параметра и значение
if ':' in line:
# Разделяем только по первому вхождению ':'
# чтобы корректно обрабатывать значения с двоеточиями
parts = line.split(':', 1)
# Извлекаем и очищаем имя параметра и значение
param_name = parts[0].strip()
param_value = parts[1].strip() if len(parts) > 1 else ''
# Добавляем в словарь
if param_name: # добавляем только если имя параметра не пустое
result[param_name] = param_value
logger.info(f"Информация о базе данных {base_name} получена успешно")
return result
except Exception as e:
logger.error(f"Ошибка при получении информации о базе данных: {e}")
raise Exception(f"Ошибка при получении информации о базе данных: {e}")
def base_info_update(self, base_name: str, db_server: str, db_name: str, db_user: str, db_password: str,
infobase_user: str, infobase_password: str,
scheduled_jobs_deny: str = "off", sessions_deny: str = "off") -> Optional[str]:
"""
Обновляет информацию о базе данных 1С
Args:
base_name: Имя базы данных 1С
db_server: Сервер PostgreSQL
db_name: Имя базы данных PostgreSQL
db_user: Пользователь PostgreSQL
db_password: Пароль PostgreSQL
infobase_user: Пользователь информационной базы
infobase_password: Пароль пользователя информационной базы
scheduled_jobs_deny: Запрет запланированных заданий (on/off), по умолчанию "off"
sessions_deny: Запрет сеансов (on/off), по умолчанию "off"
Returns:
str: ID обновленной базы данных или None при ошибке
"""
self._validate_config()
# Валидация входных данных
for param_name, param_value in [
('base_name', base_name),
('db_server', db_server),
('db_name', db_name),
('db_user', db_user),
('infobase_user', infobase_user),
('infobase_password', infobase_password)
]:
if not param_value or not isinstance(param_value, str) or not param_value.strip():
raise ValueError(f"{param_name} должен быть непустой строкой")
# db_password может быть пустой строкой, проверяем только тип
if not isinstance(db_password, str):
raise ValueError("db_password должен быть строкой")
# Валидация scheduled_jobs_deny и sessions_deny
if scheduled_jobs_deny not in ["on", "off"]:
raise ValueError("scheduled_jobs_deny должен быть 'on' или 'off'")
if sessions_deny not in ["on", "off"]:
raise ValueError("sessions_deny должен быть 'on' или 'off'")
try:
logger.info(f"Обновление информации о базе данных {base_name}")
base_id = self.base_id(base_name)
if not base_id:
logger.error(f"База данных '{base_name}' не найдена")
raise Exception(f"База данных '{base_name}' не найдена")
logger.info(f"Обновление параметров базы данных {base_name} (ID: {base_id})")
std, err = self.ssh.cmd(
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
f"infobase update --cluster={self.cluster_id()} "
f"--infobase={base_id} --dbms=PostgreSQL "
f"--db-server={db_server} --db-name={db_name} "
f"--db-user={db_user} --db-pwd={db_password} "
f"--infobase-user={infobase_user} --infobase-pwd='{infobase_password}' "
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}' "
f"--scheduled-jobs-deny={scheduled_jobs_deny} --sessions-deny={sessions_deny}"
)
if err and err.strip():
logger.error(f"Ошибка обновления базы данных: {err}")
raise Exception(f"Ошибка обновления базы данных: {err}")
# Проверяем, что обновление прошло успешно
for base in self.base_list():
if base['name'] and len(base['name']) > 0 and base['name'][0] == base_name:
if base['id'] and len(base['id']) > 0:
logger.info(f"Информация о базе данных {base_name} обновлена успешно")
return base['id'][0]
logger.warning(f"Не удалось подтвердить обновление базы данных {base_name}")
return None
except Exception as e:
logger.error(f"Ошибка при обновлении информации о базе данных: {e}")
raise Exception(f"Ошибка при обновлении информации о базе данных: {e}")