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:
2025-08-04 12:03:45 +03:00
parent ccbe0c669d
commit 46261f066b
7 changed files with 1152 additions and 678 deletions

322
benadis_rac_helpers.go Normal file
View 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
}