- Installed and configured SCST with 7 handlers - Installed and configured mhVTL with 2 Quantum libraries and 8 LTO-8 drives - Implemented all VTL API endpoints (8/9 working) - Fixed NULL device_path handling in drives endpoint - Added comprehensive error handling and validation - Implemented async tape load/unload operations - Created SCST installation guide for Ubuntu 24.04 - Created mhVTL installation and configuration guide - Added VTL testing guide and automated test scripts - All core API tests passing (89% success rate) Infrastructure status: - PostgreSQL: Configured with proper permissions - SCST: Active with kernel module loaded - mhVTL: 2 libraries (Quantum Scalar i500, Scalar i40) - mhVTL: 8 drives (all Quantum ULTRIUM-HH8 LTO-8) - Calypso API: 8/9 VTL endpoints functional Documentation added: - src/srs-technical-spec-documents/scst-installation.md - src/srs-technical-spec-documents/mhvtl-installation.md - VTL-TESTING-GUIDE.md - scripts/test-vtl.sh Co-Authored-By: Warp <agent@warp.dev>
178 lines
5.1 KiB
Go
178 lines
5.1 KiB
Go
package system
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
)
|
|
|
|
// Service handles system management operations
|
|
type Service struct {
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewService creates a new system service
|
|
func NewService(log *logger.Logger) *Service {
|
|
return &Service{
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// ServiceStatus represents a systemd service status
|
|
type ServiceStatus struct {
|
|
Name string `json:"name"`
|
|
ActiveState string `json:"active_state"`
|
|
SubState string `json:"sub_state"`
|
|
LoadState string `json:"load_state"`
|
|
Description string `json:"description"`
|
|
Since time.Time `json:"since,omitempty"`
|
|
}
|
|
|
|
// GetServiceStatus retrieves the status of a systemd service
|
|
func (s *Service) GetServiceStatus(ctx context.Context, serviceName string) (*ServiceStatus, error) {
|
|
cmd := exec.CommandContext(ctx, "systemctl", "show", serviceName,
|
|
"--property=ActiveState,SubState,LoadState,Description,ActiveEnterTimestamp",
|
|
"--value", "--no-pager")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get service status: %w", err)
|
|
}
|
|
|
|
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
if len(lines) < 4 {
|
|
return nil, fmt.Errorf("invalid service status output")
|
|
}
|
|
|
|
status := &ServiceStatus{
|
|
Name: serviceName,
|
|
ActiveState: strings.TrimSpace(lines[0]),
|
|
SubState: strings.TrimSpace(lines[1]),
|
|
LoadState: strings.TrimSpace(lines[2]),
|
|
Description: strings.TrimSpace(lines[3]),
|
|
}
|
|
|
|
// Parse timestamp if available
|
|
if len(lines) > 4 && lines[4] != "" {
|
|
if t, err := time.Parse("Mon 2006-01-02 15:04:05 MST", strings.TrimSpace(lines[4])); err == nil {
|
|
status.Since = t
|
|
}
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
// ListServices lists all Calypso-related services
|
|
func (s *Service) ListServices(ctx context.Context) ([]ServiceStatus, error) {
|
|
services := []string{
|
|
"calypso-api",
|
|
"scst",
|
|
"iscsi-scst",
|
|
"mhvtl",
|
|
"postgresql",
|
|
}
|
|
|
|
var statuses []ServiceStatus
|
|
for _, serviceName := range services {
|
|
status, err := s.GetServiceStatus(ctx, serviceName)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to get service status", "service", serviceName, "error", err)
|
|
continue
|
|
}
|
|
statuses = append(statuses, *status)
|
|
}
|
|
|
|
return statuses, nil
|
|
}
|
|
|
|
// RestartService restarts a systemd service
|
|
func (s *Service) RestartService(ctx context.Context, serviceName string) error {
|
|
cmd := exec.CommandContext(ctx, "systemctl", "restart", serviceName)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to restart service: %s: %w", string(output), err)
|
|
}
|
|
|
|
s.logger.Info("Service restarted", "service", serviceName)
|
|
return nil
|
|
}
|
|
|
|
// GetJournalLogs retrieves journald logs for a service
|
|
func (s *Service) GetJournalLogs(ctx context.Context, serviceName string, lines int) ([]map[string]interface{}, error) {
|
|
cmd := exec.CommandContext(ctx, "journalctl",
|
|
"-u", serviceName,
|
|
"-n", fmt.Sprintf("%d", lines),
|
|
"-o", "json",
|
|
"--no-pager")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get logs: %w", err)
|
|
}
|
|
|
|
var logs []map[string]interface{}
|
|
linesOutput := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
for _, line := range linesOutput {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var logEntry map[string]interface{}
|
|
if err := json.Unmarshal([]byte(line), &logEntry); err == nil {
|
|
logs = append(logs, logEntry)
|
|
}
|
|
}
|
|
|
|
return logs, nil
|
|
}
|
|
|
|
// GenerateSupportBundle generates a diagnostic support bundle
|
|
func (s *Service) GenerateSupportBundle(ctx context.Context, outputPath string) error {
|
|
// Create bundle directory
|
|
cmd := exec.CommandContext(ctx, "mkdir", "-p", outputPath)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to create bundle directory: %w", err)
|
|
}
|
|
|
|
// Collect system information
|
|
commands := map[string][]string{
|
|
"system_info": {"uname", "-a"},
|
|
"disk_usage": {"df", "-h"},
|
|
"memory": {"free", "-h"},
|
|
"scst_status": {"scstadmin", "-list_target"},
|
|
"services": {"systemctl", "list-units", "--type=service", "--state=running"},
|
|
}
|
|
|
|
for name, cmdArgs := range commands {
|
|
cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
s.logger.Warn("Failed to collect info", "command", name, "error", err)
|
|
continue
|
|
}
|
|
|
|
// Write to file
|
|
filePath := fmt.Sprintf("%s/%s.txt", outputPath, name)
|
|
if err := exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("cat > %s", filePath)).Run(); err == nil {
|
|
exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("echo '%s' > %s", string(output), filePath)).Run()
|
|
}
|
|
}
|
|
|
|
// Collect journal logs
|
|
services := []string{"calypso-api", "scst", "iscsi-scst"}
|
|
for _, service := range services {
|
|
cmd := exec.CommandContext(ctx, "journalctl", "-u", service, "-n", "1000", "--no-pager")
|
|
output, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
filePath := fmt.Sprintf("%s/journal_%s.log", outputPath, service)
|
|
exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("echo '%s' > %s", string(output), filePath)).Run()
|
|
}
|
|
}
|
|
|
|
s.logger.Info("Support bundle generated", "path", outputPath)
|
|
return nil
|
|
}
|
|
|