334 lines
9.6 KiB
Go
334 lines
9.6 KiB
Go
package rac
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os/exec"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
"git.benadis.ru/gitops/benadis-rac/internal/config"
|
||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||
"git.benadis.ru/gitops/benadis-rac/internal/logger"
|
||
)
|
||
|
||
// Client представляет клиент для работы с RAC
|
||
type Client struct {
|
||
config *config.AppConfig
|
||
logger logger.Logger
|
||
}
|
||
|
||
// NewClient создает новый RAC клиент
|
||
func NewClient(cfg *config.AppConfig, log logger.Logger) *Client {
|
||
return &Client{
|
||
config: cfg,
|
||
logger: log,
|
||
}
|
||
}
|
||
|
||
// ServiceModeParams параметры для управления сервисным режимом
|
||
type ServiceModeParams struct {
|
||
ClusterUUID string
|
||
InfobaseUUID string
|
||
Enable bool
|
||
DeniedMessage string
|
||
PermissionCode string
|
||
}
|
||
|
||
// ExecuteCommand выполняет RAC команду с retry логикой
|
||
func (c *Client) ExecuteCommand(ctx context.Context, args []string) (string, error) {
|
||
var lastErr error
|
||
retryCount := c.config.Config.RetryCount
|
||
if retryCount == 0 {
|
||
retryCount = constants.DEFAULT_RETRY_COUNT
|
||
}
|
||
|
||
retryDelay, err := c.config.Config.GetRetryDelay()
|
||
if err != nil {
|
||
c.logger.Warn(constants.LogMsgFailedToParseRetryDelay, "error", err)
|
||
retryDelay = constants.DEFAULT_RETRY_DELAY
|
||
}
|
||
|
||
for attempt := 1; attempt <= retryCount; attempt++ {
|
||
c.logger.Debug(constants.LogMsgExecutingRACCommand, "attempt", attempt, "max_attempts", retryCount)
|
||
c.logger.DebugCommand(constants.LogMsgRACCommand, append([]string{c.config.Config.RACPath}, args...))
|
||
|
||
output, err := c.executeCommandOnce(ctx, args)
|
||
if err == nil {
|
||
c.logger.Debug(constants.LogMsgRACCommandExecutedSuccessfully, "output_length", len(output))
|
||
return output, nil
|
||
}
|
||
|
||
lastErr = err
|
||
c.logger.Warn(constants.LogMsgRACCommandFailed, "attempt", attempt, "error", err)
|
||
|
||
if attempt < retryCount {
|
||
c.logger.Debug(constants.LogMsgRetryingAfterDelay, "delay", retryDelay)
|
||
select {
|
||
case <-ctx.Done():
|
||
return "", ctx.Err()
|
||
case <-time.After(retryDelay):
|
||
// Продолжаем
|
||
}
|
||
}
|
||
}
|
||
|
||
c.logger.Error(constants.LogMsgRACCommandFailedAfterAllRetries, "error", lastErr)
|
||
return "", fmt.Errorf(constants.ErrCommandFailed, retryCount, lastErr)
|
||
}
|
||
|
||
// executeCommandOnce выполняет RAC команду один раз
|
||
func (c *Client) executeCommandOnce(ctx context.Context, args []string) (string, error) {
|
||
cmdTimeout, err := c.config.Config.GetCommandTimeout()
|
||
if err != nil {
|
||
return "", fmt.Errorf(constants.ErrFailedToParseCommandTimeout, err)
|
||
}
|
||
ctx, cancel := context.WithTimeout(ctx, cmdTimeout)
|
||
defer cancel()
|
||
|
||
cmd := exec.CommandContext(ctx, c.config.Config.RACPath, args...)
|
||
output, err := cmd.CombinedOutput()
|
||
|
||
if err != nil {
|
||
// Преобразуем вывод в корректную кодировку
|
||
outputStr := convertToUTF8(output)
|
||
return "", fmt.Errorf(constants.ErrRACCommandFailed, err, outputStr)
|
||
}
|
||
|
||
return convertToUTF8(output), nil
|
||
}
|
||
|
||
// GetClusterUUID получает UUID кластера по имени сервера
|
||
func (c *Client) GetClusterUUID(ctx context.Context) (string, error) {
|
||
c.logger.Info(constants.LogMsgGettingClusterUUID)
|
||
|
||
args := []string{
|
||
c.config.GetRACAddress(),
|
||
"cluster", "list",
|
||
}
|
||
|
||
output, err := c.ExecuteCommand(ctx, args)
|
||
if err != nil {
|
||
return "", fmt.Errorf(constants.ErrGetClusterList, err)
|
||
}
|
||
|
||
// Парсим вывод для получения UUID кластера
|
||
lines := strings.Split(output, "\n")
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
if strings.HasPrefix(line, "cluster") {
|
||
parts := strings.Split(line, ":")
|
||
if len(parts) >= 2 {
|
||
uuid := strings.TrimSpace(parts[1])
|
||
c.logger.Debug(constants.LogMsgFoundClusterUUID, "uuid", uuid)
|
||
return uuid, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
return "", fmt.Errorf(constants.ErrClusterUUIDNotFound)
|
||
}
|
||
|
||
// GetInfobaseUUID получает UUID информационной базы по имени
|
||
func (c *Client) GetInfobaseUUID(ctx context.Context, clusterUUID string) (string, error) {
|
||
c.logger.Info(constants.LogMsgGettingInfobaseUUID, "cluster_uuid", clusterUUID)
|
||
|
||
// Получаем учетные данные кластера (пока используем пустое имя проекта для глобальных настроек)
|
||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||
|
||
args := []string{
|
||
c.config.GetRACAddress(),
|
||
"infobase", "summary", "list",
|
||
"--cluster=" + clusterUUID,
|
||
"--cluster-user=" + clusterUser,
|
||
"--cluster-pwd=" + clusterPwd,
|
||
}
|
||
|
||
output, err := c.ExecuteCommand(ctx, args)
|
||
if err != nil {
|
||
return "", fmt.Errorf(constants.ErrGetInfobaseList, err)
|
||
}
|
||
|
||
// Парсим вывод для поиска информационной базы по имени
|
||
lines := strings.Split(output, "\n")
|
||
var currentInfobaseUUID string
|
||
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
if strings.HasPrefix(line, "infobase") {
|
||
parts := strings.Split(line, ":")
|
||
if len(parts) >= 2 {
|
||
currentInfobaseUUID = strings.TrimSpace(parts[1])
|
||
}
|
||
} else if strings.HasPrefix(line, "name") && currentInfobaseUUID != "" {
|
||
parts := strings.Split(line, ":")
|
||
if len(parts) >= 2 {
|
||
name := strings.TrimSpace(parts[1])
|
||
if name == c.config.Project.ServiceMode.BaseName {
|
||
c.logger.Debug(constants.LogMsgFoundInfobaseUUID, "uuid", currentInfobaseUUID, "name", name)
|
||
return currentInfobaseUUID, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return "", fmt.Errorf(constants.ErrInfobaseUUIDNotFound, c.config.Project.ServiceMode.BaseName)
|
||
}
|
||
|
||
// convertToUTF8 преобразует байты из CP866 (DOS) или Windows-1251 в UTF-8
|
||
func convertToUTF8(data []byte) string {
|
||
// Проверяем, является ли строка уже валидным UTF-8
|
||
if utf8.Valid(data) {
|
||
return string(data)
|
||
}
|
||
|
||
// Преобразование из CP866 (DOS) в UTF-8
|
||
// Это основная кодировка для сообщений об ошибках RAC
|
||
runes := make([]rune, 0, len(data))
|
||
for _, b := range data {
|
||
if b < 128 {
|
||
// ASCII символы остаются без изменений
|
||
runes = append(runes, rune(b))
|
||
} else {
|
||
// Преобразование символов CP866 в Unicode
|
||
switch b {
|
||
// Кириллические символы CP866
|
||
case 0x80:
|
||
runes = append(runes, 'А') // А
|
||
case 0x81:
|
||
runes = append(runes, 'Б') // Б
|
||
case 0x82:
|
||
runes = append(runes, 'В') // В
|
||
case 0x83:
|
||
runes = append(runes, 'Г') // Г
|
||
case 0x84:
|
||
runes = append(runes, 'Д') // Д
|
||
case 0x85:
|
||
runes = append(runes, 'Е') // Е
|
||
case 0x86:
|
||
runes = append(runes, 'Ж') // Ж
|
||
case 0x87:
|
||
runes = append(runes, 'З') // З
|
||
case 0x88:
|
||
runes = append(runes, 'И') // И
|
||
case 0x89:
|
||
runes = append(runes, 'Й') // Й
|
||
case 0x8A:
|
||
runes = append(runes, 'К') // К
|
||
case 0x8B:
|
||
runes = append(runes, 'Л') // Л
|
||
case 0x8C:
|
||
runes = append(runes, 'М') // М
|
||
case 0x8D:
|
||
runes = append(runes, 'Н') // Н
|
||
case 0x8E:
|
||
runes = append(runes, 'О') // О
|
||
case 0x8F:
|
||
runes = append(runes, 'П') // П
|
||
case 0x90:
|
||
runes = append(runes, 'Р') // Р
|
||
case 0x91:
|
||
runes = append(runes, 'С') // С
|
||
case 0x92:
|
||
runes = append(runes, 'Т') // Т
|
||
case 0x93:
|
||
runes = append(runes, 'У') // У
|
||
case 0x94:
|
||
runes = append(runes, 'Ф') // Ф
|
||
case 0x95:
|
||
runes = append(runes, 'Х') // Х
|
||
case 0x96:
|
||
runes = append(runes, 'Ц') // Ц
|
||
case 0x97:
|
||
runes = append(runes, 'Ч') // Ч
|
||
case 0x98:
|
||
runes = append(runes, 'Ш') // Ш
|
||
case 0x99:
|
||
runes = append(runes, 'Щ') // Щ
|
||
case 0x9A:
|
||
runes = append(runes, 'Ъ') // Ъ
|
||
case 0x9B:
|
||
runes = append(runes, 'Ы') // Ы
|
||
case 0x9C:
|
||
runes = append(runes, 'Ь') // Ь
|
||
case 0x9D:
|
||
runes = append(runes, 'Э') // Э
|
||
case 0x9E:
|
||
runes = append(runes, 'Ю') // Ю
|
||
case 0x9F:
|
||
runes = append(runes, 'Я') // Я
|
||
case 0xA0:
|
||
runes = append(runes, 'а') // а
|
||
case 0xA1:
|
||
runes = append(runes, 'б') // б
|
||
case 0xA2:
|
||
runes = append(runes, 'в') // в
|
||
case 0xA3:
|
||
runes = append(runes, 'г') // г
|
||
case 0xA4:
|
||
runes = append(runes, 'д') // д
|
||
case 0xA5:
|
||
runes = append(runes, 'е') // е
|
||
case 0xA6:
|
||
runes = append(runes, 'ж') // ж
|
||
case 0xA7:
|
||
runes = append(runes, 'з') // з
|
||
case 0xA8:
|
||
runes = append(runes, 'и') // и
|
||
case 0xA9:
|
||
runes = append(runes, 'й') // й
|
||
case 0xAA:
|
||
runes = append(runes, 'к') // к
|
||
case 0xAB:
|
||
runes = append(runes, 'л') // л
|
||
case 0xAC:
|
||
runes = append(runes, 'м') // м
|
||
case 0xAD:
|
||
runes = append(runes, 'н') // н
|
||
case 0xAE:
|
||
runes = append(runes, 'о') // о
|
||
case 0xAF:
|
||
runes = append(runes, 'п') // п
|
||
case 0xE0:
|
||
runes = append(runes, 'р') // р
|
||
case 0xE1:
|
||
runes = append(runes, 'с') // с
|
||
case 0xE2:
|
||
runes = append(runes, 'т') // т
|
||
case 0xE3:
|
||
runes = append(runes, 'у') // у
|
||
case 0xE4:
|
||
runes = append(runes, 'ф') // ф
|
||
case 0xE5:
|
||
runes = append(runes, 'х') // х
|
||
case 0xE6:
|
||
runes = append(runes, 'ц') // ц
|
||
case 0xE7:
|
||
runes = append(runes, 'ч') // ч
|
||
case 0xE8:
|
||
runes = append(runes, 'ш') // ш
|
||
case 0xE9:
|
||
runes = append(runes, 'щ') // щ
|
||
case 0xEA:
|
||
runes = append(runes, 'ъ') // ъ
|
||
case 0xEB:
|
||
runes = append(runes, 'ы') // ы
|
||
case 0xEC:
|
||
runes = append(runes, 'ь') // ь
|
||
case 0xED:
|
||
runes = append(runes, 'э') // э
|
||
case 0xEE:
|
||
runes = append(runes, 'ю') // ю
|
||
case 0xEF:
|
||
runes = append(runes, 'я') // я
|
||
default:
|
||
// Для неизвестных символов оставляем как есть
|
||
runes = append(runes, rune(b))
|
||
}
|
||
}
|
||
}
|
||
|
||
return string(runes)
|
||
}
|