Добавляю уже существующий проект в репозиторий GIT
This commit is contained in:
400
c1_cluster.py
Normal file
400
c1_cluster.py
Normal 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}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user