Init
This commit is contained in:
333
internal/rac/rac.go
Normal file
333
internal/rac/rac.go
Normal file
@@ -0,0 +1,333 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user