Files
cursor_ai/modules/c1_cluster.py

404 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Модуль для работы с кластером 1С через SSH
"""
import logging
from typing import Optional, List, Dict
from .logger import get_logger
from .protocols import SSHOperationsBase
logger = get_logger("c1_cluster")
class C1ClusterOperations(SSHOperationsBase):
"""
Класс для операций с кластером 1С через SSH.
Реализует контракт операционного модуля (ssh: SSHProtocol).
"""
def __init__(self, ssh_client, srv_1c: str = "", c1_claster_user: str = "", c1_claster_pass: str = ""):
"""
Args:
ssh_client: Экземпляр, реализующий SSHProtocol (SSHBase, SSHClient).
srv_1c: Имя LXC контейнера с 1С
c1_claster_user: Пользователь кластера 1С
c1_claster_pass: Пароль кластера 1С
"""
super().__init__(ssh_client)
self.srv_1c: str = srv_1c
self.c1_claster_user: str = c1_claster_user
self.c1_claster_pass: str = 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}")