refactor: restructure library to expose public API directly
feat: add new helper methods for RAC operations docs: add comprehensive library usage documentation chore: move example to examples/library_usage directory
This commit is contained in:
252
LIBRARY_USAGE.md
Normal file
252
LIBRARY_USAGE.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
# Использование benadis-rac как библиотеки
|
||||||
|
|
||||||
|
После рефакторинга библиотека benadis-rac может использоваться без импорта внутренних пакетов.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.benadis.ru/gitops/benadis-rac
|
||||||
|
```
|
||||||
|
|
||||||
|
## Импорт
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "git.benadis.ru/gitops/benadis-rac"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Основное использование
|
||||||
|
|
||||||
|
### Способ 1: Использование функции ManageServiceMode (рекомендуется)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
benadis_rac "git.benadis.ru/gitops/benadis-rac"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Создаем конфигурацию
|
||||||
|
config := &benadis_rac.Config{
|
||||||
|
// RAC configuration
|
||||||
|
RACPath: "C:\\Program Files\\1cv8\\8.3.25.1257\\bin\\rac.exe",
|
||||||
|
ConnectionTimeout: 30 * time.Second,
|
||||||
|
CommandTimeout: 60 * time.Second,
|
||||||
|
RetryCount: 3,
|
||||||
|
RetryDelay: 5 * time.Second,
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
ServerHost: "localhost",
|
||||||
|
ServerPort: 1541,
|
||||||
|
RACPort: 1545,
|
||||||
|
ServerName: "server1c",
|
||||||
|
BaseName: "test_base",
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
ClusterAdmin: "admin",
|
||||||
|
ClusterAdminPassword: "password",
|
||||||
|
DBAdmin: "dbadmin",
|
||||||
|
DBAdminPassword: "dbpassword",
|
||||||
|
|
||||||
|
// Service mode settings
|
||||||
|
DeniedMessage: "Система находится на техническом обслуживании",
|
||||||
|
PermissionCode: "maintenance",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем значения по умолчанию
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
|
// Валидируем конфигурацию
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем логгер
|
||||||
|
logger := benadis_rac.NewLogger("info")
|
||||||
|
|
||||||
|
// Создаем контекст
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Включаем сервисный режим
|
||||||
|
err := benadis_rac.ManageServiceMode(ctx, config, logger, true)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выключаем сервисный режим
|
||||||
|
err = benadis_rac.ManageServiceMode(ctx, config, logger, false)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Способ 2: Использование клиента
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
benadis_rac "git.benadis.ru/gitops/benadis-rac"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Создаем конфигурацию (аналогично способу 1)
|
||||||
|
config := &benadis_rac.Config{
|
||||||
|
// ... настройки конфигурации
|
||||||
|
}
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
|
// Создаем клиент
|
||||||
|
client, err := benadis_rac.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Проверяем текущий статус
|
||||||
|
enabled, err := client.GetServiceModeStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Включаем сервисный режим
|
||||||
|
err = client.EnableServiceMode(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выключаем сервисный режим
|
||||||
|
err = client.DisableServiceMode(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура конфигурации
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
// RAC configuration
|
||||||
|
RACPath string // Путь к исполняемому файлу RAC
|
||||||
|
ConnectionTimeout time.Duration // Таймаут подключения
|
||||||
|
CommandTimeout time.Duration // Таймаут выполнения команды
|
||||||
|
RetryCount int // Количество повторных попыток
|
||||||
|
RetryDelay time.Duration // Задержка между попытками
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
ServerHost string // Хост сервера 1C
|
||||||
|
ServerPort int // Порт сервера 1C
|
||||||
|
RACPort int // Порт RAC
|
||||||
|
ServerName string // Имя сервера
|
||||||
|
BaseName string // Имя информационной базы
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
ClusterAdmin string // Администратор кластера
|
||||||
|
ClusterAdminPassword string // Пароль администратора кластера
|
||||||
|
DBAdmin string // Администратор ИБ
|
||||||
|
DBAdminPassword string // Пароль администратора ИБ
|
||||||
|
|
||||||
|
// Service mode settings
|
||||||
|
DeniedMessage string // Сообщение при блокировке
|
||||||
|
PermissionCode string // Код разрешения
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Интерфейс логгера
|
||||||
|
|
||||||
|
Вы можете использовать встроенный логгер или реализовать свой:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Logger interface {
|
||||||
|
Debug(msg string, args ...any)
|
||||||
|
Info(msg string, args ...any)
|
||||||
|
Warn(msg string, args ...any)
|
||||||
|
Error(msg string, args ...any)
|
||||||
|
DebugCommand(msg string, command []string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание встроенного логгера
|
||||||
|
logger := benadis_rac.NewLogger("info") // debug, info, warn, error
|
||||||
|
```
|
||||||
|
|
||||||
|
## Методы управления
|
||||||
|
|
||||||
|
### ManageServiceMode
|
||||||
|
Основная функция для управления сервисным режимом:
|
||||||
|
```go
|
||||||
|
func ManageServiceMode(ctx context.Context, config *Config, logger Logger, enable bool) error
|
||||||
|
```
|
||||||
|
|
||||||
|
### Методы клиента
|
||||||
|
- `EnableServiceMode(ctx context.Context) error` - включить сервисный режим
|
||||||
|
- `DisableServiceMode(ctx context.Context) error` - выключить сервисный режим
|
||||||
|
- `GetServiceModeStatus(ctx context.Context) (bool, error)` - получить статус сервисного режима
|
||||||
|
|
||||||
|
## Значения по умолчанию
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
DefaultDeniedMessage = "Техническое обслуживание. Попробуйте позже."
|
||||||
|
DefaultPermissionCode = "service-mode"
|
||||||
|
DefaultRACPort = 1545
|
||||||
|
DefaultConnectionTimeout = 30 * time.Second
|
||||||
|
DefaultCommandTimeout = 60 * time.Second
|
||||||
|
DefaultRetryCount = 3
|
||||||
|
DefaultRetryDelay = 5 * time.Second
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Валидация конфигурации
|
||||||
|
|
||||||
|
Обязательные поля:
|
||||||
|
- `ServerHost` - хост сервера 1C
|
||||||
|
- `RACPath` - путь к исполняемому файлу RAC
|
||||||
|
- `BaseName` - имя информационной базы
|
||||||
|
- `ClusterAdmin` - администратор кластера
|
||||||
|
- `DBAdmin` - администратор ИБ
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
Все методы возвращают детализированные ошибки с контекстом:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := client.EnableServiceMode(ctx); err != nil {
|
||||||
|
log.Printf("Failed to enable service mode: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Миграция с предыдущей версии
|
||||||
|
|
||||||
|
### Было (с внутренними пакетами):
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"git.benadis.ru/gitops/benadis-rac/internal/config"
|
||||||
|
"git.benadis.ru/gitops/benadis-rac/internal/logger"
|
||||||
|
"git.benadis.ru/gitops/benadis-rac/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Загрузка из файлов конфигурации
|
||||||
|
appConfig, err := config.LoadConfig("config.yaml", "secret.yaml", "project.yaml")
|
||||||
|
log := logger.NewLogger("info")
|
||||||
|
svc := service.NewServiceModeService(appConfig, log)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Стало (только публичный API):
|
||||||
|
```go
|
||||||
|
import benadis_rac "git.benadis.ru/gitops/benadis-rac"
|
||||||
|
|
||||||
|
// Прямая передача значений в структуру
|
||||||
|
config := &benadis_rac.Config{
|
||||||
|
RACPath: "path/to/rac",
|
||||||
|
ServerHost: "localhost",
|
||||||
|
// ... другие поля
|
||||||
|
}
|
||||||
|
logger := benadis_rac.NewLogger("info")
|
||||||
|
benadis_rac.ManageServiceMode(ctx, config, logger, true)
|
||||||
|
```
|
||||||
528
benadis_rac.go
528
benadis_rac.go
@@ -4,12 +4,51 @@ package benadis_rac
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"git.benadis.ru/gitops/benadis-rac/internal/config"
|
"os"
|
||||||
"git.benadis.ru/gitops/benadis-rac/internal/logger"
|
"os/exec"
|
||||||
"git.benadis.ru/gitops/benadis-rac/internal/service"
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Logger интерфейс для логирования
|
||||||
|
type Logger interface {
|
||||||
|
Debug(msg string, args ...any)
|
||||||
|
Info(msg string, args ...any)
|
||||||
|
Warn(msg string, args ...any)
|
||||||
|
Error(msg string, args ...any)
|
||||||
|
DebugCommand(msg string, command []string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config конфигурация для управления сервисным режимом
|
||||||
|
type Config struct {
|
||||||
|
// RAC configuration
|
||||||
|
RACPath string // Путь к исполняемому файлу RAC
|
||||||
|
ConnectionTimeout time.Duration // Таймаут подключения
|
||||||
|
CommandTimeout time.Duration // Таймаут выполнения команды
|
||||||
|
RetryCount int // Количество повторных попыток
|
||||||
|
RetryDelay time.Duration // Задержка между попытками
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
ServerHost string // Хост сервера 1C
|
||||||
|
ServerPort int // Порт сервера 1C
|
||||||
|
RACPort int // Порт RAC
|
||||||
|
ServerName string // Имя сервера
|
||||||
|
BaseName string // Имя информационной базы
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
ClusterAdmin string // Администратор кластера
|
||||||
|
ClusterAdminPassword string // Пароль администратора кластера
|
||||||
|
DBAdmin string // Администратор ИБ
|
||||||
|
DBAdminPassword string // Пароль администратора ИБ
|
||||||
|
|
||||||
|
// Service mode settings
|
||||||
|
DeniedMessage string // Сообщение при блокировке
|
||||||
|
PermissionCode string // Код разрешения
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceModeManager интерфейс для управления сервисным режимом
|
// ServiceModeManager интерфейс для управления сервисным режимом
|
||||||
type ServiceModeManager interface {
|
type ServiceModeManager interface {
|
||||||
// EnableServiceMode включает сервисный режим
|
// EnableServiceMode включает сервисный режим
|
||||||
@@ -24,101 +63,450 @@ type ServiceModeManager interface {
|
|||||||
|
|
||||||
// Client клиент для работы с 1C сервисным режимом
|
// Client клиент для работы с 1C сервисным режимом
|
||||||
type Client struct {
|
type Client struct {
|
||||||
service *service.ServiceModeService
|
config *Config
|
||||||
|
logger Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config конфигурация для создания клиента
|
// SlogLogger реализация Logger с использованием slog
|
||||||
type Config struct {
|
type SlogLogger struct {
|
||||||
ConfigPath string // Путь к файлу конфигурации
|
logger *slog.Logger
|
||||||
SecretPath string // Путь к файлу с секретами
|
}
|
||||||
ProjectPath string // Путь к файлу проекта
|
|
||||||
LogLevel string // Уровень логирования (Debug, Info, Warn, Error)
|
// NewLogger создает новый логгер
|
||||||
|
func NewLogger(level string) Logger {
|
||||||
|
var logLevel slog.Level
|
||||||
|
switch strings.ToLower(level) {
|
||||||
|
case "debug":
|
||||||
|
logLevel = slog.LevelDebug
|
||||||
|
case "info":
|
||||||
|
logLevel = slog.LevelInfo
|
||||||
|
case "warn":
|
||||||
|
logLevel = slog.LevelWarn
|
||||||
|
case "error":
|
||||||
|
logLevel = slog.LevelError
|
||||||
|
default:
|
||||||
|
logLevel = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
|
Level: logLevel,
|
||||||
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||||
|
if a.Key == slog.SourceKey {
|
||||||
|
source := a.Value.Any().(*slog.Source)
|
||||||
|
source.File = path.Base(source.File)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return &SlogLogger{
|
||||||
|
logger: slog.New(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SlogLogger) Debug(msg string, args ...any) {
|
||||||
|
l.logger.Debug(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SlogLogger) Info(msg string, args ...any) {
|
||||||
|
l.logger.Info(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SlogLogger) Warn(msg string, args ...any) {
|
||||||
|
l.logger.Warn(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SlogLogger) Error(msg string, args ...any) {
|
||||||
|
l.logger.Error(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SlogLogger) DebugCommand(msg string, command []string) {
|
||||||
|
maskedCommand := maskPasswords(command)
|
||||||
|
l.logger.Debug(msg, "command", strings.Join(maskedCommand, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskPasswords(command []string) []string {
|
||||||
|
masked := make([]string, len(command))
|
||||||
|
copy(masked, command)
|
||||||
|
|
||||||
|
passwordFlags := []string{"--cluster-pwd", "--infobase-pwd"}
|
||||||
|
|
||||||
|
for i, arg := range masked {
|
||||||
|
for _, flag := range passwordFlags {
|
||||||
|
if arg == flag && i+1 < len(masked) {
|
||||||
|
masked[i+1] = "***"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient создает новый клиент для управления сервисным режимом
|
// NewClient создает новый клиент для управления сервисным режимом
|
||||||
func NewClient(cfg Config) (*Client, error) {
|
func NewClient(config *Config) (*Client, error) {
|
||||||
if cfg.ConfigPath == "" {
|
if config == nil {
|
||||||
cfg.ConfigPath = "config.yaml"
|
return nil, fmt.Errorf("config cannot be nil")
|
||||||
}
|
|
||||||
if cfg.SecretPath == "" {
|
|
||||||
cfg.SecretPath = "secret.yaml"
|
|
||||||
}
|
|
||||||
if cfg.ProjectPath == "" {
|
|
||||||
cfg.ProjectPath = "project.yaml"
|
|
||||||
}
|
|
||||||
if cfg.LogLevel == "" {
|
|
||||||
cfg.LogLevel = "Info"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем конфигурацию
|
|
||||||
appConfig, err := config.LoadConfig(cfg.ConfigPath, cfg.SecretPath, cfg.ProjectPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Переопределяем уровень логирования если указан
|
|
||||||
if appConfig.Project != nil && appConfig.Project.ServiceMode != nil {
|
|
||||||
appConfig.Project.ServiceMode.LogLevel = cfg.LogLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Валидируем конфигурацию
|
|
||||||
if err := appConfig.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("config validation failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем логгер
|
// Создаем логгер
|
||||||
logLevel := "Info"
|
logger := NewLogger("info")
|
||||||
if appConfig.Project != nil && appConfig.Project.ServiceMode != nil && appConfig.Project.ServiceMode.LogLevel != "" {
|
|
||||||
logLevel = appConfig.Project.ServiceMode.LogLevel
|
|
||||||
}
|
|
||||||
log := logger.NewLogger(logLevel)
|
|
||||||
|
|
||||||
// Создаем сервис
|
|
||||||
svc := service.NewServiceModeService(appConfig, log)
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
service: svc,
|
config: config,
|
||||||
|
logger: logger,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ManageServiceMode основная функция для управления сервисным режимом
|
||||||
|
func ManageServiceMode(ctx context.Context, config *Config, logger Logger, enable bool) error {
|
||||||
|
if config == nil {
|
||||||
|
return fmt.Errorf("config cannot be nil")
|
||||||
|
}
|
||||||
|
if logger == nil {
|
||||||
|
return fmt.Errorf("logger cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
config: config,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
return client.EnableServiceMode(ctx)
|
||||||
|
}
|
||||||
|
return client.DisableServiceMode(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// EnableServiceMode включает сервисный режим
|
// EnableServiceMode включает сервисный режим
|
||||||
func (c *Client) EnableServiceMode(ctx context.Context) error {
|
func (c *Client) EnableServiceMode(ctx context.Context) error {
|
||||||
return c.service.EnableServiceMode(ctx)
|
c.logger.Info("Starting service mode enablement")
|
||||||
|
|
||||||
|
// Получаем UUID кластера
|
||||||
|
clusterUUID, err := c.getClusterUUID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get cluster UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем UUID информационной базы
|
||||||
|
infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get infobase UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Включаем сервисный режим
|
||||||
|
if err := c.setServiceMode(ctx, clusterUUID, infobaseUUID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to enable service mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Завершаем все активные сессии
|
||||||
|
if err := c.terminateAllSessions(ctx, clusterUUID, infobaseUUID); err != nil {
|
||||||
|
c.logger.Warn("Failed to terminate all sessions", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем статус
|
||||||
|
if err := c.verifyServiceMode(ctx, clusterUUID, infobaseUUID, true); err != nil {
|
||||||
|
return fmt.Errorf("service mode verification failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("Service mode enabled successfully")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableServiceMode выключает сервисный режим
|
// DisableServiceMode выключает сервисный режим
|
||||||
func (c *Client) DisableServiceMode(ctx context.Context) error {
|
func (c *Client) DisableServiceMode(ctx context.Context) error {
|
||||||
return c.service.DisableServiceMode(ctx)
|
c.logger.Info("Starting service mode disablement")
|
||||||
|
|
||||||
|
// Получаем UUID кластера
|
||||||
|
clusterUUID, err := c.getClusterUUID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get cluster UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем UUID информационной базы
|
||||||
|
infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get infobase UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выключаем сервисный режим
|
||||||
|
if err := c.setServiceMode(ctx, clusterUUID, infobaseUUID, false); err != nil {
|
||||||
|
return fmt.Errorf("failed to disable service mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем статус
|
||||||
|
if err := c.verifyServiceMode(ctx, clusterUUID, infobaseUUID, false); err != nil {
|
||||||
|
return fmt.Errorf("service mode verification failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("Service mode disabled successfully")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceModeStatus получает текущий статус сервисного режима
|
// GetServiceModeStatus получает текущий статус сервисного режима
|
||||||
// Возвращает true если сервисный режим включен, false если выключен
|
|
||||||
func (c *Client) GetServiceModeStatus(ctx context.Context) (bool, error) {
|
func (c *Client) GetServiceModeStatus(ctx context.Context) (bool, error) {
|
||||||
return c.service.GetServiceModeStatus(ctx)
|
// Получаем UUID кластера
|
||||||
|
clusterUUID, err := c.getClusterUUID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get cluster UUID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientFromConfig создает клиент из готовой конфигурации
|
// Получаем UUID информационной базы
|
||||||
func NewClientFromConfig(appConfig *config.AppConfig, logLevel string) (*Client, error) {
|
infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID)
|
||||||
if logLevel != "" && appConfig.Project != nil && appConfig.Project.ServiceMode != nil {
|
if err != nil {
|
||||||
appConfig.Project.ServiceMode.LogLevel = logLevel
|
return false, fmt.Errorf("failed to get infobase UUID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Валидируем конфигурацию
|
// Получаем информацию об информационной базе
|
||||||
if err := appConfig.Validate(); err != nil {
|
args := []string{
|
||||||
return nil, fmt.Errorf("config validation failed: %w", err)
|
c.config.RACPath,
|
||||||
|
"infobase", "info",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--infobase=" + infobaseUUID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
"--infobase-user=" + c.config.DBAdmin,
|
||||||
|
"--infobase-pwd=" + c.config.DBAdminPassword,
|
||||||
|
c.getRACAddress(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем логгер
|
output, err := c.executeCommand(ctx, args)
|
||||||
logLevelToUse := "Info"
|
if err != nil {
|
||||||
if appConfig.Project != nil && appConfig.Project.ServiceMode != nil && appConfig.Project.ServiceMode.LogLevel != "" {
|
return false, fmt.Errorf("failed to get infobase info: %w", err)
|
||||||
logLevelToUse = appConfig.Project.ServiceMode.LogLevel
|
|
||||||
}
|
}
|
||||||
log := logger.NewLogger(logLevelToUse)
|
|
||||||
|
|
||||||
// Создаем сервис
|
// Проверяем статус сервисного режима
|
||||||
svc := service.NewServiceModeService(appConfig, log)
|
return strings.Contains(output, "sessions-deny") && strings.Contains(output, "on"), nil
|
||||||
|
}
|
||||||
return &Client{
|
|
||||||
service: svc,
|
// Константы
|
||||||
}, nil
|
const (
|
||||||
|
DefaultDeniedMessage = "Техническое обслуживание. Попробуйте позже."
|
||||||
|
DefaultPermissionCode = "service-mode"
|
||||||
|
TechnicalMaintenanceMessage = "Техническое обслуживание"
|
||||||
|
UUIDLength = 36
|
||||||
|
UUIDDashCount = 4
|
||||||
|
DefaultRACPort = 1545
|
||||||
|
DefaultConnectionTimeout = 30 * time.Second
|
||||||
|
DefaultCommandTimeout = 60 * time.Second
|
||||||
|
DefaultRetryCount = 3
|
||||||
|
DefaultRetryDelay = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// getRACAddress возвращает адрес RAC сервера
|
||||||
|
func (c *Client) getRACAddress() string {
|
||||||
|
port := c.config.RACPort
|
||||||
|
if port == 0 {
|
||||||
|
port = DefaultRACPort
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", c.config.ServerHost, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeCommand выполняет RAC команду с retry логикой
|
||||||
|
func (c *Client) executeCommand(ctx context.Context, args []string) (string, error) {
|
||||||
|
var lastErr error
|
||||||
|
retryCount := c.config.RetryCount
|
||||||
|
if retryCount == 0 {
|
||||||
|
retryCount = DefaultRetryCount
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDelay := c.config.RetryDelay
|
||||||
|
if retryDelay == 0 {
|
||||||
|
retryDelay = DefaultRetryDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt := 1; attempt <= retryCount; attempt++ {
|
||||||
|
output, err := c.executeCommandOnce(ctx, args)
|
||||||
|
if err == nil {
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = err
|
||||||
|
c.logger.Warn("Command execution failed", "attempt", attempt, "error", err)
|
||||||
|
|
||||||
|
if attempt < retryCount {
|
||||||
|
c.logger.Info("Retrying after delay", "delay", retryDelay)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return "", ctx.Err()
|
||||||
|
case <-time.After(retryDelay):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("command failed after %d attempts: %w", retryCount, lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeCommandOnce выполняет RAC команду один раз
|
||||||
|
func (c *Client) executeCommandOnce(ctx context.Context, args []string) (string, error) {
|
||||||
|
c.logger.DebugCommand("Executing RAC command", args)
|
||||||
|
|
||||||
|
cmdTimeout := c.config.CommandTimeout
|
||||||
|
if cmdTimeout == 0 {
|
||||||
|
cmdTimeout = DefaultCommandTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdCtx, cancel := context.WithTimeout(ctx, cmdTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(cmdCtx, args[0], args[1:]...)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("RAC command failed", "error", err, "output", string(output))
|
||||||
|
return "", fmt.Errorf("RAC command failed: %w, output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
utf8Output := convertToUTF8(output)
|
||||||
|
c.logger.Debug("RAC command executed successfully", "output_length", len(utf8Output))
|
||||||
|
return utf8Output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToUTF8 конвертирует байты в UTF-8 строку
|
||||||
|
func convertToUTF8(data []byte) string {
|
||||||
|
if utf8.Valid(data) {
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пытаемся декодировать как Windows-1251
|
||||||
|
runes := make([]rune, 0, len(data))
|
||||||
|
for _, b := range data {
|
||||||
|
if b < 128 {
|
||||||
|
runes = append(runes, rune(b))
|
||||||
|
} else {
|
||||||
|
// Простая таблица конвертации для основных символов Windows-1251
|
||||||
|
switch b {
|
||||||
|
case 0xC0: // А
|
||||||
|
runes = append(runes, 'А')
|
||||||
|
case 0xC1: // Б
|
||||||
|
runes = append(runes, 'Б')
|
||||||
|
case 0xC2: // В
|
||||||
|
runes = append(runes, 'В')
|
||||||
|
case 0xC3: // Г
|
||||||
|
runes = append(runes, 'Г')
|
||||||
|
case 0xC4: // Д
|
||||||
|
runes = append(runes, 'Д')
|
||||||
|
case 0xC5: // Е
|
||||||
|
runes = append(runes, 'Е')
|
||||||
|
case 0xC6: // Ж
|
||||||
|
runes = append(runes, 'Ж')
|
||||||
|
case 0xC7: // З
|
||||||
|
runes = append(runes, 'З')
|
||||||
|
case 0xC8: // И
|
||||||
|
runes = append(runes, 'И')
|
||||||
|
case 0xC9: // Й
|
||||||
|
runes = append(runes, 'Й')
|
||||||
|
case 0xCA: // К
|
||||||
|
runes = append(runes, 'К')
|
||||||
|
case 0xCB: // Л
|
||||||
|
runes = append(runes, 'Л')
|
||||||
|
case 0xCC: // М
|
||||||
|
runes = append(runes, 'М')
|
||||||
|
case 0xCD: // Н
|
||||||
|
runes = append(runes, 'Н')
|
||||||
|
case 0xCE: // О
|
||||||
|
runes = append(runes, 'О')
|
||||||
|
case 0xCF: // П
|
||||||
|
runes = append(runes, 'П')
|
||||||
|
case 0xD0: // Р
|
||||||
|
runes = append(runes, 'Р')
|
||||||
|
case 0xD1: // С
|
||||||
|
runes = append(runes, 'С')
|
||||||
|
case 0xD2: // Т
|
||||||
|
runes = append(runes, 'Т')
|
||||||
|
case 0xD3: // У
|
||||||
|
runes = append(runes, 'У')
|
||||||
|
case 0xD4: // Ф
|
||||||
|
runes = append(runes, 'Ф')
|
||||||
|
case 0xD5: // Х
|
||||||
|
runes = append(runes, 'Х')
|
||||||
|
case 0xD6: // Ц
|
||||||
|
runes = append(runes, 'Ц')
|
||||||
|
case 0xD7: // Ч
|
||||||
|
runes = append(runes, 'Ч')
|
||||||
|
case 0xD8: // Ш
|
||||||
|
runes = append(runes, 'Ш')
|
||||||
|
case 0xD9: // Щ
|
||||||
|
runes = append(runes, 'Щ')
|
||||||
|
case 0xDA: // Ъ
|
||||||
|
runes = append(runes, 'Ъ')
|
||||||
|
case 0xDB: // Ы
|
||||||
|
runes = append(runes, 'Ы')
|
||||||
|
case 0xDC: // Ь
|
||||||
|
runes = append(runes, 'Ь')
|
||||||
|
case 0xDD: // Э
|
||||||
|
runes = append(runes, 'Э')
|
||||||
|
case 0xDE: // Ю
|
||||||
|
runes = append(runes, 'Ю')
|
||||||
|
case 0xDF: // Я
|
||||||
|
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, 'п')
|
||||||
|
case 0xF0: // р
|
||||||
|
runes = append(runes, 'р')
|
||||||
|
case 0xF1: // с
|
||||||
|
runes = append(runes, 'с')
|
||||||
|
case 0xF2: // т
|
||||||
|
runes = append(runes, 'т')
|
||||||
|
case 0xF3: // у
|
||||||
|
runes = append(runes, 'у')
|
||||||
|
case 0xF4: // ф
|
||||||
|
runes = append(runes, 'ф')
|
||||||
|
case 0xF5: // х
|
||||||
|
runes = append(runes, 'х')
|
||||||
|
case 0xF6: // ц
|
||||||
|
runes = append(runes, 'ц')
|
||||||
|
case 0xF7: // ч
|
||||||
|
runes = append(runes, 'ч')
|
||||||
|
case 0xF8: // ш
|
||||||
|
runes = append(runes, 'ш')
|
||||||
|
case 0xF9: // щ
|
||||||
|
runes = append(runes, 'щ')
|
||||||
|
case 0xFA: // ъ
|
||||||
|
runes = append(runes, 'ъ')
|
||||||
|
case 0xFB: // ы
|
||||||
|
runes = append(runes, 'ы')
|
||||||
|
case 0xFC: // ь
|
||||||
|
runes = append(runes, 'ь')
|
||||||
|
case 0xFD: // э
|
||||||
|
runes = append(runes, 'э')
|
||||||
|
case 0xFE: // ю
|
||||||
|
runes = append(runes, 'ю')
|
||||||
|
case 0xFF: // я
|
||||||
|
runes = append(runes, 'я')
|
||||||
|
default:
|
||||||
|
runes = append(runes, rune(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|||||||
322
benadis_rac_helpers.go
Normal file
322
benadis_rac_helpers.go
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
package benadis_rac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getClusterUUID получает UUID кластера
|
||||||
|
func (c *Client) getClusterUUID(ctx context.Context) (string, error) {
|
||||||
|
c.logger.Debug("Getting cluster UUID")
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"cluster", "list",
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get cluster list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем UUID кластера в выводе
|
||||||
|
uuidRegex := regexp.MustCompile(`cluster\s*:\s*([a-fA-F0-9-]{36})`)
|
||||||
|
matches := uuidRegex.FindStringSubmatch(output)
|
||||||
|
if len(matches) < 2 {
|
||||||
|
return "", fmt.Errorf("cluster UUID not found in output")
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterUUID := matches[1]
|
||||||
|
c.logger.Debug("Found cluster UUID", "uuid", clusterUUID)
|
||||||
|
return clusterUUID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInfobaseUUID получает UUID информационной базы
|
||||||
|
func (c *Client) getInfobaseUUID(ctx context.Context, clusterUUID string) (string, error) {
|
||||||
|
c.logger.Debug("Getting infobase UUID", "cluster", clusterUUID)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"infobase", "summary", "list",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get infobase list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем информационную базу по имени
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
var currentInfobase string
|
||||||
|
var currentUUID string
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "infobase") {
|
||||||
|
// Извлекаем UUID из строки вида "infobase : uuid"
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
currentUUID = strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(line, "name") {
|
||||||
|
// Извлекаем имя из строки вида "name : basename"
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
currentInfobase = strings.TrimSpace(parts[1])
|
||||||
|
if currentInfobase == c.config.BaseName && currentUUID != "" {
|
||||||
|
c.logger.Debug("Found infobase UUID", "uuid", currentUUID, "name", currentInfobase)
|
||||||
|
return currentUUID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("infobase UUID not found for name: %s", c.config.BaseName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setServiceMode устанавливает сервисный режим
|
||||||
|
func (c *Client) setServiceMode(ctx context.Context, clusterUUID, infobaseUUID string, enable bool) error {
|
||||||
|
var action string
|
||||||
|
if enable {
|
||||||
|
c.logger.Info("Enabling service mode")
|
||||||
|
action = "on"
|
||||||
|
} else {
|
||||||
|
c.logger.Info("Disabling service mode")
|
||||||
|
action = "off"
|
||||||
|
}
|
||||||
|
|
||||||
|
deniedMessage := c.config.DeniedMessage
|
||||||
|
if deniedMessage == "" {
|
||||||
|
deniedMessage = DefaultDeniedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionCode := c.config.PermissionCode
|
||||||
|
if permissionCode == "" {
|
||||||
|
permissionCode = DefaultPermissionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"infobase", "update",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--infobase=" + infobaseUUID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
"--infobase-user=" + c.config.DBAdmin,
|
||||||
|
"--infobase-pwd=" + c.config.DBAdminPassword,
|
||||||
|
"--sessions-deny=" + action,
|
||||||
|
"--scheduled-jobs-deny=" + action,
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
args = append(args, "--denied-message="+deniedMessage)
|
||||||
|
args = append(args, "--permission-code="+permissionCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
if enable {
|
||||||
|
return fmt.Errorf("failed to enable service mode: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to disable service mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminateAllSessions завершает все активные сессии
|
||||||
|
func (c *Client) terminateAllSessions(ctx context.Context, clusterUUID, infobaseUUID string) error {
|
||||||
|
c.logger.Info("Terminating all sessions")
|
||||||
|
|
||||||
|
// Получаем список сессий
|
||||||
|
sessions, err := c.getSessions(ctx, clusterUUID, infobaseUUID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sessions) == 0 {
|
||||||
|
c.logger.Info("No active sessions found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("Found active sessions", "count", len(sessions))
|
||||||
|
|
||||||
|
// Завершаем каждую сессию
|
||||||
|
for _, sessionID := range sessions {
|
||||||
|
if err := c.terminateSession(ctx, clusterUUID, sessionID); err != nil {
|
||||||
|
c.logger.Warn("Failed to terminate session", "session", sessionID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.logger.Debug("Session terminated", "session", sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("All sessions termination completed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSessions получает список активных сессий
|
||||||
|
func (c *Client) getSessions(ctx context.Context, clusterUUID, infobaseUUID string) ([]string, error) {
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"session", "list",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--infobase=" + infobaseUUID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get session list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Парсим вывод для извлечения ID сессий
|
||||||
|
var sessions []string
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "session") {
|
||||||
|
// Извлекаем UUID из строки вида "session : uuid"
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
sessionID := strings.TrimSpace(parts[1])
|
||||||
|
if isValidUUID(sessionID) {
|
||||||
|
sessions = append(sessions, sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminateSession завершает конкретную сессию
|
||||||
|
func (c *Client) terminateSession(ctx context.Context, clusterUUID, sessionID string) error {
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"session", "terminate",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--session=" + sessionID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
"--message=" + TechnicalMaintenanceMessage,
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to terminate session %s: %w", sessionID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyServiceMode проверяет статус сервисного режима
|
||||||
|
func (c *Client) verifyServiceMode(ctx context.Context, clusterUUID, infobaseUUID string, expectedEnabled bool) error {
|
||||||
|
c.logger.Debug("Verifying service mode", "expected_enabled", expectedEnabled)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
c.config.RACPath,
|
||||||
|
"infobase", "info",
|
||||||
|
"--cluster=" + clusterUUID,
|
||||||
|
"--infobase=" + infobaseUUID,
|
||||||
|
"--cluster-user=" + c.config.ClusterAdmin,
|
||||||
|
"--cluster-pwd=" + c.config.ClusterAdminPassword,
|
||||||
|
"--infobase-user=" + c.config.DBAdmin,
|
||||||
|
"--infobase-pwd=" + c.config.DBAdminPassword,
|
||||||
|
c.getRACAddress(),
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := c.executeCommand(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get infobase info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем статус sessions-deny
|
||||||
|
expectedSessionsDeny := "off"
|
||||||
|
if expectedEnabled {
|
||||||
|
expectedSessionsDeny = "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output, "sessions-deny") || !strings.Contains(output, expectedSessionsDeny) {
|
||||||
|
return fmt.Errorf("sessions-deny verification failed: expected %s, got output: %s", expectedSessionsDeny, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем статус scheduled-jobs-deny
|
||||||
|
expectedJobsDeny := "off"
|
||||||
|
if expectedEnabled {
|
||||||
|
expectedJobsDeny = "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output, "scheduled-jobs-deny") || !strings.Contains(output, expectedJobsDeny) {
|
||||||
|
return fmt.Errorf("scheduled-jobs-deny verification failed: expected %s, got output: %s", expectedJobsDeny, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug("Service mode verification successful")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidUUID проверяет, является ли строка валидным UUID
|
||||||
|
func isValidUUID(uuid string) bool {
|
||||||
|
if len(uuid) != UUIDLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dashCount := strings.Count(uuid, "-")
|
||||||
|
return dashCount == UUIDDashCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults устанавливает значения по умолчанию для конфигурации
|
||||||
|
func (c *Config) SetDefaults() {
|
||||||
|
if c.ConnectionTimeout == 0 {
|
||||||
|
c.ConnectionTimeout = DefaultConnectionTimeout
|
||||||
|
}
|
||||||
|
if c.CommandTimeout == 0 {
|
||||||
|
c.CommandTimeout = DefaultCommandTimeout
|
||||||
|
}
|
||||||
|
if c.RetryCount == 0 {
|
||||||
|
c.RetryCount = DefaultRetryCount
|
||||||
|
}
|
||||||
|
if c.RetryDelay == 0 {
|
||||||
|
c.RetryDelay = DefaultRetryDelay
|
||||||
|
}
|
||||||
|
if c.RACPort == 0 {
|
||||||
|
c.RACPort = DefaultRACPort
|
||||||
|
}
|
||||||
|
if c.DeniedMessage == "" {
|
||||||
|
c.DeniedMessage = DefaultDeniedMessage
|
||||||
|
}
|
||||||
|
if c.PermissionCode == "" {
|
||||||
|
c.PermissionCode = DefaultPermissionCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate проверяет корректность конфигурации
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if c.ServerHost == "" {
|
||||||
|
return fmt.Errorf("server_host is required")
|
||||||
|
}
|
||||||
|
if c.RACPath == "" {
|
||||||
|
return fmt.Errorf("rac_path is required")
|
||||||
|
}
|
||||||
|
if c.BaseName == "" {
|
||||||
|
return fmt.Errorf("base_name is required")
|
||||||
|
}
|
||||||
|
if c.ClusterAdmin == "" {
|
||||||
|
return fmt.Errorf("cluster_admin is required")
|
||||||
|
}
|
||||||
|
if c.DBAdmin == "" {
|
||||||
|
return fmt.Errorf("db_admin is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
266
example/main.go
266
example/main.go
@@ -1,266 +0,0 @@
|
|||||||
// Package main демонстрирует использование библиотеки benadis-rac
|
|
||||||
// для управления сервисным режимом 1C
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
benadisrac "git.benadis.ru/gitops/benadis-rac"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExampleConfig содержит примеры конфигурации для демонстрации
|
|
||||||
type ExampleConfig struct {
|
|
||||||
ConfigPath string
|
|
||||||
SecretPath string
|
|
||||||
ProjectPath string
|
|
||||||
LogLevel string
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultConfig возвращает конфигурацию по умолчанию
|
|
||||||
func getDefaultConfig() ExampleConfig {
|
|
||||||
return ExampleConfig{
|
|
||||||
ConfigPath: "../config.yaml",
|
|
||||||
SecretPath: "../secret.yaml",
|
|
||||||
ProjectPath: "../project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// demonstrateBasicUsage демонстрирует базовое использование библиотеки
|
|
||||||
func demonstrateBasicUsage() error {
|
|
||||||
fmt.Println("=== Демонстрация базового использования библиотеки GitOps RAC ===")
|
|
||||||
|
|
||||||
// Получаем конфигурацию
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
|
|
||||||
// Создаем клиент
|
|
||||||
fmt.Println("Создание клиента...")
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: cfg.LogLevel,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка создания клиента: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Клиент успешно создан")
|
|
||||||
|
|
||||||
// Создаем контекст с таймаутом
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Получаем текущий статус
|
|
||||||
fmt.Println("\nПроверка текущего статуса сервисного режима...")
|
|
||||||
status, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка получения статуса: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("✓ Текущий статус сервисного режима: %t\n", status)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// demonstrateServiceModeOperations демонстрирует операции с сервисным режимом
|
|
||||||
func demonstrateServiceModeOperations() error {
|
|
||||||
fmt.Println("\n=== Демонстрация операций с сервисным режимом ===")
|
|
||||||
|
|
||||||
// Создаем клиент
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: "Debug", // Используем Debug для подробного логирования
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка создания клиента: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Получаем начальный статус
|
|
||||||
initialStatus, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка получения начального статуса: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Начальный статус: %t\n", initialStatus)
|
|
||||||
|
|
||||||
// Если сервисный режим выключен, включаем его
|
|
||||||
if !initialStatus {
|
|
||||||
fmt.Println("\nВключение сервисного режима...")
|
|
||||||
if err := client.EnableServiceMode(ctx); err != nil {
|
|
||||||
return fmt.Errorf("ошибка включения сервисного режима: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Сервисный режим включен")
|
|
||||||
|
|
||||||
// Проверяем статус после включения
|
|
||||||
status, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка проверки статуса после включения: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("✓ Статус после включения: %t\n", status)
|
|
||||||
|
|
||||||
// Ждем немного
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
// Выключаем сервисный режим
|
|
||||||
fmt.Println("\nВыключение сервисного режима...")
|
|
||||||
if err := client.DisableServiceMode(ctx); err != nil {
|
|
||||||
return fmt.Errorf("ошибка выключения сервисного режима: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Сервисный режим выключен")
|
|
||||||
} else {
|
|
||||||
fmt.Println("\nВыключение сервисного режима...")
|
|
||||||
if err := client.DisableServiceMode(ctx); err != nil {
|
|
||||||
return fmt.Errorf("ошибка выключения сервисного режима: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Сервисный режим выключен")
|
|
||||||
|
|
||||||
// Проверяем статус после выключения
|
|
||||||
status, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка проверки статуса после выключения: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("✓ Статус после выключения: %t\n", status)
|
|
||||||
|
|
||||||
// Ждем немного
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
// Включаем сервисный режим обратно
|
|
||||||
fmt.Println("\nВключение сервисного режима обратно...")
|
|
||||||
if err := client.EnableServiceMode(ctx); err != nil {
|
|
||||||
return fmt.Errorf("ошибка включения сервисного режима: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Сервисный режим включен обратно")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Финальная проверка статуса
|
|
||||||
finalStatus, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ошибка получения финального статуса: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("\n✓ Финальный статус: %t\n", finalStatus)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// demonstrateErrorHandling демонстрирует обработку ошибок
|
|
||||||
func demonstrateErrorHandling() {
|
|
||||||
fmt.Println("\n=== Демонстрация обработки ошибок ===")
|
|
||||||
|
|
||||||
// Попытка создать клиент с неверными путями
|
|
||||||
fmt.Println("Попытка создания клиента с неверными путями...")
|
|
||||||
_, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: "nonexistent-config.yaml",
|
|
||||||
SecretPath: "nonexistent-secret.yaml",
|
|
||||||
ProjectPath: "nonexistent-project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("✓ Ожидаемая ошибка: %v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("⚠ Неожиданно: ошибка не возникла")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Попытка создать клиент с неверным уровнем логирования
|
|
||||||
fmt.Println("\nПопытка создания клиента с неверным уровнем логирования...")
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
cfg.LogLevel = "InvalidLevel"
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: cfg.LogLevel,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("✓ Ошибка при неверном уровне логирования: %v\n", err)
|
|
||||||
} else if client != nil {
|
|
||||||
fmt.Println("✓ Клиент создан, но может использовать уровень логирования по умолчанию")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUsageInstructions выводит инструкции по использованию
|
|
||||||
func printUsageInstructions() {
|
|
||||||
fmt.Println("\n=== Инструкции по использованию библиотеки GitOps RAC ===")
|
|
||||||
fmt.Println("\n1. Импортируйте библиотеку:")
|
|
||||||
fmt.Println(` import benadisrac "git.benadis.ru/gitops/benadis-rac/"`)
|
|
||||||
|
|
||||||
fmt.Println("\n2. Создайте конфигурацию:")
|
|
||||||
fmt.Println(` cfg := benadisrac.Config{`)
|
|
||||||
fmt.Println(` ConfigPath: "config.yaml",`)
|
|
||||||
fmt.Println(` SecretPath: "secret.yaml",`)
|
|
||||||
fmt.Println(` ProjectPath: "project.yaml",`)
|
|
||||||
fmt.Println(` LogLevel: "Info",`)
|
|
||||||
fmt.Println(` }`)
|
|
||||||
|
|
||||||
fmt.Println("\n3. Создайте клиент:")
|
|
||||||
fmt.Println(` client, err := benadisrac.NewClient(cfg)`)
|
|
||||||
fmt.Println(` if err != nil {`)
|
|
||||||
fmt.Println(` log.Fatal(err)`)
|
|
||||||
fmt.Println(` }`)
|
|
||||||
|
|
||||||
fmt.Println("\n4. Используйте методы клиента:")
|
|
||||||
fmt.Println(` ctx := context.Background()`)
|
|
||||||
fmt.Println(` status, err := client.GetServiceModeStatus(ctx)`)
|
|
||||||
fmt.Println(` err = client.EnableServiceMode(ctx)`)
|
|
||||||
fmt.Println(` err = client.DisableServiceMode(ctx)`)
|
|
||||||
|
|
||||||
fmt.Println("\n5. Доступные уровни логирования: Debug, Info, Warn, Error")
|
|
||||||
fmt.Println("\n6. Все операции поддерживают context.Context для управления таймаутами")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("GitOps RAC Library Example")
|
|
||||||
fmt.Println("==========================")
|
|
||||||
|
|
||||||
// Проверяем аргументы командной строки
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "--help", "-h":
|
|
||||||
printUsageInstructions()
|
|
||||||
return
|
|
||||||
case "--basic":
|
|
||||||
if err := demonstrateBasicUsage(); err != nil {
|
|
||||||
log.Printf("Ошибка в базовой демонстрации: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case "--operations":
|
|
||||||
if err := demonstrateServiceModeOperations(); err != nil {
|
|
||||||
log.Printf("Ошибка в демонстрации операций: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case "--errors":
|
|
||||||
demonstrateErrorHandling()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Выполняем все демонстрации по умолчанию
|
|
||||||
fmt.Println("Запуск всех демонстраций...")
|
|
||||||
fmt.Println("Используйте флаги: --basic, --operations, --errors, --help")
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// Базовое использование
|
|
||||||
if err := demonstrateBasicUsage(); err != nil {
|
|
||||||
log.Printf("Ошибка в базовой демонстрации: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Операции с сервисным режимом (только если базовая демонстрация прошла успешно)
|
|
||||||
if err := demonstrateServiceModeOperations(); err != nil {
|
|
||||||
log.Printf("Ошибка в демонстрации операций: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка ошибок
|
|
||||||
demonstrateErrorHandling()
|
|
||||||
|
|
||||||
// Инструкции
|
|
||||||
printUsageInstructions()
|
|
||||||
|
|
||||||
fmt.Println("\n=== Демонстрация завершена ===")
|
|
||||||
}
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
benadisrac "git.benadis.ru/gitops/benadis-rac"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestGetDefaultConfig тестирует функцию получения конфигурации по умолчанию
|
|
||||||
func TestGetDefaultConfig(t *testing.T) {
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
|
|
||||||
if cfg.ConfigPath == "" {
|
|
||||||
t.Error("ConfigPath не должен быть пустым")
|
|
||||||
}
|
|
||||||
if cfg.SecretPath == "" {
|
|
||||||
t.Error("SecretPath не должен быть пустым")
|
|
||||||
}
|
|
||||||
if cfg.ProjectPath == "" {
|
|
||||||
t.Error("ProjectPath не должен быть пустым")
|
|
||||||
}
|
|
||||||
if cfg.LogLevel == "" {
|
|
||||||
t.Error("LogLevel не должен быть пустым")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем ожидаемые значения
|
|
||||||
expected := ExampleConfig{
|
|
||||||
ConfigPath: "../config.yaml",
|
|
||||||
SecretPath: "../secret.yaml",
|
|
||||||
ProjectPath: "../project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg != expected {
|
|
||||||
t.Errorf("Неожиданная конфигурация. Получено: %+v, ожидалось: %+v", cfg, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestClientCreation тестирует создание клиента с различными конфигурациями
|
|
||||||
func TestClientCreation(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
config benadisrac.Config
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Валидная конфигурация",
|
|
||||||
config: benadisrac.Config{
|
|
||||||
ConfigPath: "../config.yaml",
|
|
||||||
SecretPath: "../secret.yaml",
|
|
||||||
ProjectPath: "../project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Несуществующий файл конфигурации",
|
|
||||||
config: benadisrac.Config{
|
|
||||||
ConfigPath: "nonexistent.yaml",
|
|
||||||
SecretPath: "../secret.yaml",
|
|
||||||
ProjectPath: "../project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Несуществующий файл секретов",
|
|
||||||
config: benadisrac.Config{
|
|
||||||
ConfigPath: "../config.yaml",
|
|
||||||
SecretPath: "nonexistent.yaml",
|
|
||||||
ProjectPath: "../project.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Несуществующий файл проекта",
|
|
||||||
config: benadisrac.Config{
|
|
||||||
ConfigPath: "../config.yaml",
|
|
||||||
SecretPath: "../secret.yaml",
|
|
||||||
ProjectPath: "nonexistent.yaml",
|
|
||||||
LogLevel: "Info",
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Пустые пути (должны использоваться значения по умолчанию)",
|
|
||||||
config: benadisrac.Config{
|
|
||||||
LogLevel: "Debug",
|
|
||||||
},
|
|
||||||
expectError: true, // Файлы по умолчанию не существуют в example директории
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
client, err := benadisrac.NewClient(tt.config)
|
|
||||||
|
|
||||||
if tt.expectError {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Ожидалась ошибка, но её не было")
|
|
||||||
}
|
|
||||||
if client != nil {
|
|
||||||
t.Error("Клиент не должен быть создан при ошибке")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Неожиданная ошибка: %v", err)
|
|
||||||
}
|
|
||||||
if client == nil {
|
|
||||||
t.Error("Клиент должен быть создан")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestClientOperations тестирует операции клиента (требует валидную конфигурацию)
|
|
||||||
func TestClientOperations(t *testing.T) {
|
|
||||||
// Проверяем наличие файлов конфигурации
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) {
|
|
||||||
t.Skip("Пропускаем тест: файлы конфигурации не найдены")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: "Info",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Ошибка создания клиента: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Тестируем получение статуса
|
|
||||||
t.Run("GetServiceModeStatus", func(t *testing.T) {
|
|
||||||
status, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Ошибка получения статуса: %v", err)
|
|
||||||
}
|
|
||||||
// Статус может быть true или false, оба варианта валидны
|
|
||||||
t.Logf("Текущий статус сервисного режима: %t", status)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Тестируем включение сервисного режима
|
|
||||||
t.Run("EnableServiceMode", func(t *testing.T) {
|
|
||||||
err := client.EnableServiceMode(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Ошибка включения сервисного режима: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Тестируем выключение сервисного режима
|
|
||||||
t.Run("DisableServiceMode", func(t *testing.T) {
|
|
||||||
err := client.DisableServiceMode(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Ошибка выключения сервисного режима: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestClientWithContext тестирует работу с контекстом
|
|
||||||
func TestClientWithContext(t *testing.T) {
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) {
|
|
||||||
t.Skip("Пропускаем тест: файлы конфигурации не найдены")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: "Info",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Ошибка создания клиента: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Тестируем таймаут контекста
|
|
||||||
t.Run("ContextTimeout", func(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Ждем, чтобы контекст истек
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
|
||||||
|
|
||||||
_, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err == nil {
|
|
||||||
t.Log("Операция завершилась быстрее таймаута или таймаут не обрабатывается")
|
|
||||||
} else {
|
|
||||||
t.Logf("Ожидаемая ошибка таймаута: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Тестируем отмену контекста
|
|
||||||
t.Run("ContextCancellation", func(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cancel() // Отменяем контекст сразу
|
|
||||||
|
|
||||||
_, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err == nil {
|
|
||||||
t.Log("Операция завершилась до проверки отмены или отмена не обрабатывается")
|
|
||||||
} else {
|
|
||||||
t.Logf("Ожидаемая ошибка отмены: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDemonstrateBasicUsage тестирует функцию демонстрации базового использования
|
|
||||||
func TestDemonstrateBasicUsage(t *testing.T) {
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) {
|
|
||||||
t.Skip("Пропускаем тест: файлы конфигурации не найдены")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := demonstrateBasicUsage()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Ошибка в демонстрации базового использования: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDemonstrateErrorHandling тестирует функцию демонстрации обработки ошибок
|
|
||||||
func TestDemonstrateErrorHandling(t *testing.T) {
|
|
||||||
// Эта функция не должна вызывать панику
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Errorf("Функция demonstrateErrorHandling вызвала панику: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
demonstrateErrorHandling()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMainWithArguments тестирует обработку аргументов командной строки
|
|
||||||
func TestMainWithArguments(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
}{
|
|
||||||
{"Help", []string{"program", "--help"}},
|
|
||||||
{"Help short", []string{"program", "-h"}},
|
|
||||||
{"Errors", []string{"program", "--errors"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// Сохраняем оригинальные аргументы
|
|
||||||
origArgs := os.Args
|
|
||||||
defer func() { os.Args = origArgs }()
|
|
||||||
|
|
||||||
// Устанавливаем тестовые аргументы
|
|
||||||
os.Args = tt.args
|
|
||||||
|
|
||||||
// Проверяем, что функции не вызывают панику
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Errorf("Функция вызвала панику с аргументами %v: %v", tt.args, r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Тестируем отдельные функции вместо main()
|
|
||||||
switch tt.name {
|
|
||||||
case "Help", "Help short":
|
|
||||||
printUsageInstructions()
|
|
||||||
case "Errors":
|
|
||||||
demonstrateErrorHandling()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательные функции
|
|
||||||
|
|
||||||
// fileExists проверяет существование файла
|
|
||||||
func fileExists(filename string) bool {
|
|
||||||
_, err := os.Stat(filename)
|
|
||||||
return !os.IsNotExist(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkClientCreation бенчмарк создания клиента
|
|
||||||
func BenchmarkClientCreation(b *testing.B) {
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) {
|
|
||||||
b.Skip("Пропускаем бенчмарк: файлы конфигурации не найдены")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: "Info",
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
client, err := benadisrac.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Ошибка создания клиента: %v", err)
|
|
||||||
}
|
|
||||||
_ = client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkGetStatus бенчмарк получения статуса
|
|
||||||
func BenchmarkGetStatus(b *testing.B) {
|
|
||||||
cfg := getDefaultConfig()
|
|
||||||
if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) {
|
|
||||||
b.Skip("Пропускаем бенчмарк: файлы конфигурации не найдены")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := benadisrac.NewClient(benadisrac.Config{
|
|
||||||
ConfigPath: cfg.ConfigPath,
|
|
||||||
SecretPath: cfg.SecretPath,
|
|
||||||
ProjectPath: cfg.ProjectPath,
|
|
||||||
LogLevel: "Info",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Ошибка создания клиента: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := client.GetServiceModeStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Ошибка получения статуса: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
examples/library_usage/go.mod
Normal file
7
examples/library_usage/go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module example
|
||||||
|
|
||||||
|
go 1.24.5
|
||||||
|
|
||||||
|
require git.benadis.ru/gitops/benadis-rac v0.0.0
|
||||||
|
|
||||||
|
replace git.benadis.ru/gitops/benadis-rac => ../..
|
||||||
108
examples/library_usage/main.go
Normal file
108
examples/library_usage/main.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
benadisrac "git.benadis.ru/gitops/benadis-rac"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Пример 1: Использование функции ManageServiceMode (рекомендуемый способ)
|
||||||
|
log.Println("=== Пример 1: Использование ManageServiceMode ===")
|
||||||
|
|
||||||
|
// Создаем конфигурацию
|
||||||
|
config := &benadisrac.Config{
|
||||||
|
// RAC конфигурация
|
||||||
|
RACPath: "C:\\Program Files\\1cv8\\8.3.25.1257\\bin\\rac.exe",
|
||||||
|
ConnectionTimeout: 30 * time.Second,
|
||||||
|
CommandTimeout: 60 * time.Second,
|
||||||
|
RetryCount: 3,
|
||||||
|
RetryDelay: 5 * time.Second,
|
||||||
|
|
||||||
|
// Настройки сервера
|
||||||
|
ServerHost: "localhost",
|
||||||
|
ServerPort: 1541,
|
||||||
|
RACPort: 1545,
|
||||||
|
ServerName: "server1c",
|
||||||
|
BaseName: "test_base",
|
||||||
|
|
||||||
|
// Аутентификация
|
||||||
|
ClusterAdmin: "admin",
|
||||||
|
ClusterAdminPassword: "password",
|
||||||
|
DBAdmin: "dbadmin",
|
||||||
|
DBAdminPassword: "dbpassword",
|
||||||
|
|
||||||
|
// Настройки режима обслуживания
|
||||||
|
DeniedMessage: "Система находится в режиме обслуживания",
|
||||||
|
PermissionCode: "MaintenanceMode",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем значения по умолчанию
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
|
// Валидируем конфигурацию
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
log.Fatalf("Ошибка валидации конфигурации: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем логгер
|
||||||
|
logger := benadisrac.NewLogger("info")
|
||||||
|
|
||||||
|
// Создаем контекст
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Включаем режим обслуживания
|
||||||
|
log.Println("Включение режима обслуживания...")
|
||||||
|
if err := benadisrac.ManageServiceMode(ctx, config, logger, true); err != nil {
|
||||||
|
log.Printf("Ошибка включения режима обслуживания: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Режим обслуживания успешно включен")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ждем немного
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// Отключаем режим обслуживания
|
||||||
|
log.Println("Отключение режима обслуживания...")
|
||||||
|
if err := benadisrac.ManageServiceMode(ctx, config, logger, false); err != nil {
|
||||||
|
log.Printf("Ошибка отключения режима обслуживания: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Режим обслуживания успешно отключен")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пример 2: Использование Client напрямую
|
||||||
|
log.Println("\n=== Пример 2: Использование Client напрямую ===")
|
||||||
|
|
||||||
|
// Создаем клиента с той же конфигурацией
|
||||||
|
client, err := benadisrac.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Ошибка создания клиента: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем статус режима обслуживания
|
||||||
|
log.Println("Получение статуса режима обслуживания...")
|
||||||
|
status, err := client.GetServiceModeStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Ошибка получения статуса: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Текущий статус режима обслуживания: %t", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Включаем режим обслуживания
|
||||||
|
log.Println("Включение режима обслуживания через Client...")
|
||||||
|
if err := client.EnableServiceMode(ctx); err != nil {
|
||||||
|
log.Printf("Ошибка включения режима обслуживания: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Режим обслуживания успешно включен")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отключаем режим обслуживания
|
||||||
|
log.Println("Отключение режима обслуживания через Client...")
|
||||||
|
if err := client.DisableServiceMode(ctx); err != nil {
|
||||||
|
log.Printf("Ошибка отключения режима обслуживания: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Режим обслуживания успешно отключен")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user