...
This commit is contained in:
242
pkg/processmanager/interfaces/openrpc/client.go
Normal file
242
pkg/processmanager/interfaces/openrpc/client.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package openrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager/client"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Client provides a client for interacting with process manager operations via RPC
|
||||
type Client struct {
|
||||
client.BaseClient
|
||||
secret string
|
||||
}
|
||||
|
||||
// NewClient creates a new client for the process manager API
|
||||
func NewClient(socketPath, secret string) *Client {
|
||||
return &Client{
|
||||
BaseClient: *client.NewClient(socketPath, secret),
|
||||
}
|
||||
}
|
||||
|
||||
// StartProcess starts a new process with the given name and command
|
||||
func (c *Client) StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) (interfaces.ProcessStartResult, error) {
|
||||
params := map[string]interface{}{
|
||||
"name": name,
|
||||
"command": command,
|
||||
"log_enabled": logEnabled,
|
||||
"deadline": deadline,
|
||||
"cron": cron,
|
||||
"job_id": jobID,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.start", paramsJSON, "")
|
||||
if err != nil {
|
||||
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to start process: %w", err)
|
||||
}
|
||||
|
||||
// Convert result to ProcessStartResult
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var startResult interfaces.ProcessStartResult
|
||||
if err := json.Unmarshal(resultJSON, &startResult); err != nil {
|
||||
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to unmarshal process start result: %w", err)
|
||||
}
|
||||
|
||||
return startResult, nil
|
||||
}
|
||||
|
||||
// StopProcess stops a running process
|
||||
func (c *Client) StopProcess(name string) (interfaces.ProcessStopResult, error) {
|
||||
params := map[string]string{
|
||||
"name": name,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.stop", paramsJSON, "")
|
||||
if err != nil {
|
||||
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to stop process: %w", err)
|
||||
}
|
||||
|
||||
// Convert result to ProcessStopResult
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var stopResult interfaces.ProcessStopResult
|
||||
if err := json.Unmarshal(resultJSON, &stopResult); err != nil {
|
||||
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to unmarshal process stop result: %w", err)
|
||||
}
|
||||
|
||||
return stopResult, nil
|
||||
}
|
||||
|
||||
// RestartProcess restarts a process
|
||||
func (c *Client) RestartProcess(name string) (interfaces.ProcessRestartResult, error) {
|
||||
params := map[string]string{
|
||||
"name": name,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.restart", paramsJSON, "")
|
||||
if err != nil {
|
||||
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to restart process: %w", err)
|
||||
}
|
||||
|
||||
// Convert result to ProcessRestartResult
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var restartResult interfaces.ProcessRestartResult
|
||||
if err := json.Unmarshal(resultJSON, &restartResult); err != nil {
|
||||
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to unmarshal process restart result: %w", err)
|
||||
}
|
||||
|
||||
return restartResult, nil
|
||||
}
|
||||
|
||||
// DeleteProcess deletes a process from the manager
|
||||
func (c *Client) DeleteProcess(name string) (interfaces.ProcessDeleteResult, error) {
|
||||
params := map[string]string{
|
||||
"name": name,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.delete", paramsJSON, "")
|
||||
if err != nil {
|
||||
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to delete process: %w", err)
|
||||
}
|
||||
|
||||
// Convert result to ProcessDeleteResult
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var deleteResult interfaces.ProcessDeleteResult
|
||||
if err := json.Unmarshal(resultJSON, &deleteResult); err != nil {
|
||||
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to unmarshal process delete result: %w", err)
|
||||
}
|
||||
|
||||
return deleteResult, nil
|
||||
}
|
||||
|
||||
// GetProcessStatus gets the status of a process
|
||||
func (c *Client) GetProcessStatus(name string, format string) (interface{}, error) {
|
||||
params := map[string]string{
|
||||
"name": name,
|
||||
"format": format,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.status", paramsJSON, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get process status: %w", err)
|
||||
}
|
||||
|
||||
if format == "text" {
|
||||
// For text format, return the raw result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// For JSON format, convert to ProcessStatus
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var statusResult interfaces.ProcessStatus
|
||||
if err := json.Unmarshal(resultJSON, &statusResult); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal process status result: %w", err)
|
||||
}
|
||||
|
||||
return statusResult, nil
|
||||
}
|
||||
|
||||
// ListProcesses lists all processes
|
||||
func (c *Client) ListProcesses(format string) (interface{}, error) {
|
||||
params := map[string]string{
|
||||
"format": format,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.list", paramsJSON, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list processes: %w", err)
|
||||
}
|
||||
|
||||
if format == "text" {
|
||||
// For text format, return the raw result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// For JSON format, convert to []ProcessStatus
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var listResult []interfaces.ProcessStatus
|
||||
if err := json.Unmarshal(resultJSON, &listResult); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal process list result: %w", err)
|
||||
}
|
||||
|
||||
return listResult, nil
|
||||
}
|
||||
|
||||
// GetProcessLogs gets logs for a process
|
||||
func (c *Client) GetProcessLogs(name string, lines int) (interfaces.ProcessLogResult, error) {
|
||||
params := map[string]interface{}{
|
||||
"name": name,
|
||||
"lines": lines,
|
||||
}
|
||||
paramsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.Request("process.log", paramsJSON, "")
|
||||
if err != nil {
|
||||
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to get process logs: %w", err)
|
||||
}
|
||||
|
||||
// Convert result to ProcessLogResult
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
|
||||
var logResult interfaces.ProcessLogResult
|
||||
if err := json.Unmarshal(resultJSON, &logResult); err != nil {
|
||||
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to unmarshal process log result: %w", err)
|
||||
}
|
||||
|
||||
return logResult, nil
|
||||
}
|
269
pkg/processmanager/interfaces/openrpc/handler.go
Normal file
269
pkg/processmanager/interfaces/openrpc/handler.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package openrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Handler implements the OpenRPC handlers for process manager operations
|
||||
type Handler struct {
|
||||
processManager interfaces.ProcessManagerInterface
|
||||
}
|
||||
|
||||
// NewHandler creates a new RPC handler for process manager operations
|
||||
func NewHandler(processManager interfaces.ProcessManagerInterface) *Handler {
|
||||
return &Handler{
|
||||
processManager: processManager,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandlers returns a map of RPC handlers for the OpenRPC manager
|
||||
func (h *Handler) GetHandlers() map[string]openrpcmanager.RPCHandler {
|
||||
return map[string]openrpcmanager.RPCHandler{
|
||||
"process.start": h.handleProcessStart,
|
||||
"process.stop": h.handleProcessStop,
|
||||
"process.restart": h.handleProcessRestart,
|
||||
"process.delete": h.handleProcessDelete,
|
||||
"process.status": h.handleProcessStatus,
|
||||
"process.list": h.handleProcessList,
|
||||
"process.log": h.handleProcessLog,
|
||||
}
|
||||
}
|
||||
|
||||
// handleProcessStart handles the process.start method
|
||||
func (h *Handler) handleProcessStart(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
LogEnabled bool `json:"log_enabled"`
|
||||
Deadline int `json:"deadline"`
|
||||
Cron string `json:"cron"`
|
||||
JobID string `json:"job_id"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
err := h.processManager.StartProcess(
|
||||
request.Name,
|
||||
request.Command,
|
||||
request.LogEnabled,
|
||||
request.Deadline,
|
||||
request.Cron,
|
||||
request.JobID,
|
||||
)
|
||||
|
||||
result := interfaces.ProcessStartResult{
|
||||
Success: err == nil,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Get the process info to return the PID
|
||||
procInfo, err := h.processManager.GetProcessStatus(request.Name)
|
||||
if err != nil {
|
||||
result.Message = fmt.Sprintf("Process started but failed to get status: %v", err)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.PID = procInfo.PID
|
||||
result.Message = fmt.Sprintf("Process '%s' started successfully with PID %d", request.Name, procInfo.PID)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleProcessStop handles the process.stop method
|
||||
func (h *Handler) handleProcessStop(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
err := h.processManager.StopProcess(request.Name)
|
||||
|
||||
result := interfaces.ProcessStopResult{
|
||||
Success: err == nil,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
result.Message = fmt.Sprintf("Process '%s' stopped successfully", request.Name)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleProcessRestart handles the process.restart method
|
||||
func (h *Handler) handleProcessRestart(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
err := h.processManager.RestartProcess(request.Name)
|
||||
|
||||
result := interfaces.ProcessRestartResult{
|
||||
Success: err == nil,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Get the process info to return the PID
|
||||
procInfo, err := h.processManager.GetProcessStatus(request.Name)
|
||||
if err != nil {
|
||||
result.Message = fmt.Sprintf("Process restarted but failed to get status: %v", err)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.PID = procInfo.PID
|
||||
result.Message = fmt.Sprintf("Process '%s' restarted successfully with PID %d", request.Name, procInfo.PID)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleProcessDelete handles the process.delete method
|
||||
func (h *Handler) handleProcessDelete(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
err := h.processManager.DeleteProcess(request.Name)
|
||||
|
||||
result := interfaces.ProcessDeleteResult{
|
||||
Success: err == nil,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
result.Message = fmt.Sprintf("Process '%s' deleted successfully", request.Name)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleProcessStatus handles the process.status method
|
||||
func (h *Handler) handleProcessStatus(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
procInfo, err := h.processManager.GetProcessStatus(request.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get process status: %w", err)
|
||||
}
|
||||
|
||||
if request.Format == "text" {
|
||||
// Format as text using the processmanager's FormatProcessInfo function
|
||||
textResult, err := processmanager.FormatProcessInfo(procInfo, "text")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to format process info: %w", err)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"text": textResult,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Default to JSON format
|
||||
return convertProcessInfoToStatus(procInfo), nil
|
||||
}
|
||||
|
||||
// handleProcessList handles the process.list method
|
||||
func (h *Handler) handleProcessList(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Format string `json:"format"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
processes := h.processManager.ListProcesses()
|
||||
|
||||
if request.Format == "text" {
|
||||
// Format as text using the processmanager's FormatProcessList function
|
||||
textResult, err := processmanager.FormatProcessList(processes, "text")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to format process list: %w", err)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"text": textResult,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Default to JSON format
|
||||
result := make([]interfaces.ProcessStatus, 0, len(processes))
|
||||
for _, proc := range processes {
|
||||
result = append(result, convertProcessInfoToStatus(proc))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleProcessLog handles the process.log method
|
||||
func (h *Handler) handleProcessLog(params json.RawMessage) (interface{}, error) {
|
||||
var request struct {
|
||||
Name string `json:"name"`
|
||||
Lines int `json:"lines"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &request); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
logs, err := h.processManager.GetProcessLogs(request.Name, request.Lines)
|
||||
|
||||
result := interfaces.ProcessLogResult{
|
||||
Success: err == nil,
|
||||
Message: "",
|
||||
Logs: logs,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
result.Logs = ""
|
||||
} else {
|
||||
result.Message = fmt.Sprintf("Retrieved %d lines of logs for process '%s'", request.Lines, request.Name)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// convertProcessInfoToStatus converts a ProcessInfo to a ProcessStatus
|
||||
func convertProcessInfoToStatus(info *processmanager.ProcessInfo) interfaces.ProcessStatus {
|
||||
return interfaces.ProcessStatus{
|
||||
Name: info.Name,
|
||||
Command: info.Command,
|
||||
PID: info.PID,
|
||||
Status: string(info.Status),
|
||||
CPUPercent: info.CPUPercent,
|
||||
MemoryMB: info.MemoryMB,
|
||||
StartTime: info.StartTime,
|
||||
LogEnabled: info.LogEnabled,
|
||||
Cron: info.Cron,
|
||||
JobID: info.JobID,
|
||||
Deadline: info.Deadline,
|
||||
Error: info.Error,
|
||||
}
|
||||
}
|
130
pkg/processmanager/interfaces/openrpc/rpc_test.go
Normal file
130
pkg/processmanager/interfaces/openrpc/rpc_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package openrpc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProcessManagerRPC(t *testing.T) {
|
||||
// Create a temporary directory for the socket
|
||||
tempDir, err := os.MkdirTemp("", "processmanager-rpc-test")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a socket path
|
||||
socketPath := filepath.Join(tempDir, "process-manager.sock")
|
||||
|
||||
// Create a process manager
|
||||
pm := processmanager.NewProcessManager()
|
||||
pm.SetLogsBasePath(filepath.Join(tempDir, "logs"))
|
||||
|
||||
// Create and start the server
|
||||
server, err := NewServer(pm, socketPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start the server in a goroutine
|
||||
go func() {
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
t.Logf("Error starting server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the server to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Create a client
|
||||
client := NewClient(socketPath, "")
|
||||
|
||||
// Test process start
|
||||
t.Run("StartProcess", func(t *testing.T) {
|
||||
result, err := client.StartProcess("test-process", "echo 'Hello, World!'", true, 0, "", "")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.Success)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
assert.NotZero(t, result.PID)
|
||||
})
|
||||
|
||||
// Test process status
|
||||
t.Run("GetProcessStatus", func(t *testing.T) {
|
||||
status, err := client.GetProcessStatus("test-process", "json")
|
||||
require.NoError(t, err)
|
||||
|
||||
processStatus, ok := status.(interfaces.ProcessStatus)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "test-process", processStatus.Name)
|
||||
assert.Equal(t, "echo 'Hello, World!'", processStatus.Command)
|
||||
})
|
||||
|
||||
// Test process list
|
||||
t.Run("ListProcesses", func(t *testing.T) {
|
||||
processList, err := client.ListProcesses("json")
|
||||
require.NoError(t, err)
|
||||
|
||||
processes, ok := processList.([]interfaces.ProcessStatus)
|
||||
require.True(t, ok)
|
||||
assert.NotEmpty(t, processes)
|
||||
|
||||
// Find our test process
|
||||
found := false
|
||||
for _, proc := range processes {
|
||||
if proc.Name == "test-process" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
})
|
||||
|
||||
// Test process logs
|
||||
t.Run("GetProcessLogs", func(t *testing.T) {
|
||||
// Wait a bit for logs to be generated
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
logs, err := client.GetProcessLogs("test-process", 10)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, logs.Success)
|
||||
})
|
||||
|
||||
// Test process restart
|
||||
t.Run("RestartProcess", func(t *testing.T) {
|
||||
result, err := client.RestartProcess("test-process")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.Success)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
})
|
||||
|
||||
// Test process stop
|
||||
t.Run("StopProcess", func(t *testing.T) {
|
||||
result, err := client.StopProcess("test-process")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.Success)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
})
|
||||
|
||||
// Test process delete
|
||||
t.Run("DeleteProcess", func(t *testing.T) {
|
||||
result, err := client.DeleteProcess("test-process")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.Success)
|
||||
assert.NotEmpty(t, result.Message)
|
||||
})
|
||||
|
||||
// Stop the server
|
||||
err = server.Stop()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestProcessManagerRPCWithMock tests the RPC interface with a mock process manager
|
||||
func TestProcessManagerRPCWithMock(t *testing.T) {
|
||||
// This test would use a mock implementation of the ProcessManagerInterface
|
||||
// to test the RPC layer without actually starting real processes
|
||||
t.Skip("Mock implementation test to be added")
|
||||
}
|
32
pkg/processmanager/interfaces/openrpc/schema.go
Normal file
32
pkg/processmanager/interfaces/openrpc/schema.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package openrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
)
|
||||
|
||||
// LoadSchema loads the OpenRPC schema from the embedded JSON file
|
||||
func LoadSchema() (openrpcmanager.OpenRPCSchema, error) {
|
||||
// Get the absolute path to the schema.json file
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
schemaPath := filepath.Join(filepath.Dir(filename), "schema.json")
|
||||
|
||||
// Read the schema file
|
||||
schemaBytes, err := os.ReadFile(schemaPath)
|
||||
if err != nil {
|
||||
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("failed to read schema file: %w", err)
|
||||
}
|
||||
|
||||
// Unmarshal the schema
|
||||
var schema openrpcmanager.OpenRPCSchema
|
||||
if err := json.Unmarshal(schemaBytes, &schema); err != nil {
|
||||
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("failed to unmarshal schema: %w", err)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
333
pkg/processmanager/interfaces/openrpc/schema.json
Normal file
333
pkg/processmanager/interfaces/openrpc/schema.json
Normal file
@@ -0,0 +1,333 @@
|
||||
{
|
||||
"openrpc": "1.2.6",
|
||||
"info": {
|
||||
"title": "Process Manager API",
|
||||
"version": "1.0.0",
|
||||
"description": "API for managing and monitoring processes"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "process.start",
|
||||
"description": "Start a new process with the given name and command",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"description": "Command to execute",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "log_enabled",
|
||||
"description": "Whether to enable logging for the process",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"description": "Deadline in seconds after which the process will be automatically stopped (0 for no deadline)",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cron",
|
||||
"description": "Cron expression for scheduled execution",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "job_id",
|
||||
"description": "Optional job ID for tracking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process start result",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.stop",
|
||||
"description": "Stop a running process",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process to stop",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process stop result",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.restart",
|
||||
"description": "Restart a process",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process to restart",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process restart result",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.delete",
|
||||
"description": "Delete a process from the manager",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process to delete",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process delete result",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.status",
|
||||
"description": "Get the status of a process",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"description": "Output format (json or text)",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["json", "text"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process status information",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["running", "stopped", "failed", "completed"]
|
||||
},
|
||||
"cpu_percent": {
|
||||
"type": "number"
|
||||
},
|
||||
"memory_mb": {
|
||||
"type": "number"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"log_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"cron": {
|
||||
"type": "string"
|
||||
},
|
||||
"job_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"deadline": {
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.list",
|
||||
"description": "List all processes",
|
||||
"params": [
|
||||
{
|
||||
"name": "format",
|
||||
"description": "Output format (json or text)",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["json", "text"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "List of processes",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
},
|
||||
"pid": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["running", "stopped", "failed", "completed"]
|
||||
},
|
||||
"cpu_percent": {
|
||||
"type": "number"
|
||||
},
|
||||
"memory_mb": {
|
||||
"type": "number"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"log_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"cron": {
|
||||
"type": "string"
|
||||
},
|
||||
"job_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"deadline": {
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "process.log",
|
||||
"description": "Get logs for a process",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the process",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lines",
|
||||
"description": "Number of log lines to retrieve",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": "Process logs",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"logs": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
100
pkg/processmanager/interfaces/openrpc/server.go
Normal file
100
pkg/processmanager/interfaces/openrpc/server.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package openrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// Server represents the Process Manager OpenRPC server
|
||||
type Server struct {
|
||||
processManager interfaces.ProcessManagerInterface
|
||||
socketPath string
|
||||
openRPCMgr *openrpcmanager.OpenRPCManager
|
||||
unixServer *openrpcmanager.UnixServer
|
||||
isRunning bool
|
||||
}
|
||||
|
||||
// NewServer creates a new Process Manager OpenRPC server
|
||||
func NewServer(processManager interfaces.ProcessManagerInterface, socketPath string) (*Server, error) {
|
||||
// Ensure the directory for the socket exists
|
||||
socketDir := filepath.Dir(socketPath)
|
||||
if err := os.MkdirAll(socketDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create socket directory: %w", err)
|
||||
}
|
||||
|
||||
// Load the OpenRPC schema
|
||||
schema, err := LoadSchema()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load OpenRPC schema: %w", err)
|
||||
}
|
||||
|
||||
// Create a new handler - no authentication now
|
||||
handler := NewHandler(processManager)
|
||||
|
||||
// Create a new OpenRPC manager - using empty string for auth (no authentication)
|
||||
openRPCMgr, err := openrpcmanager.NewOpenRPCManager(schema, handler.GetHandlers(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OpenRPC manager: %w", err)
|
||||
}
|
||||
|
||||
// Create a new Unix server
|
||||
unixServer, err := openrpcmanager.NewUnixServer(openRPCMgr, socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Unix server: %w", err)
|
||||
}
|
||||
|
||||
return &Server{
|
||||
processManager: processManager,
|
||||
socketPath: socketPath,
|
||||
openRPCMgr: openRPCMgr,
|
||||
unixServer: unixServer,
|
||||
isRunning: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start starts the Process Manager OpenRPC server
|
||||
func (s *Server) Start() error {
|
||||
if s.isRunning {
|
||||
return fmt.Errorf("server is already running")
|
||||
}
|
||||
|
||||
// Start the Unix server
|
||||
if err := s.unixServer.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start Unix server: %w", err)
|
||||
}
|
||||
|
||||
s.isRunning = true
|
||||
log.Printf("Process Manager OpenRPC server started on socket: %s", s.socketPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the Process Manager OpenRPC server
|
||||
func (s *Server) Stop() error {
|
||||
if !s.isRunning {
|
||||
return fmt.Errorf("server is not running")
|
||||
}
|
||||
|
||||
// Stop the Unix server
|
||||
if err := s.unixServer.Stop(); err != nil {
|
||||
return fmt.Errorf("failed to stop Unix server: %w", err)
|
||||
}
|
||||
|
||||
s.isRunning = false
|
||||
log.Printf("Process Manager OpenRPC server stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning returns whether the server is running
|
||||
func (s *Server) IsRunning() bool {
|
||||
return s.isRunning
|
||||
}
|
||||
|
||||
// SocketPath returns the socket path
|
||||
func (s *Server) SocketPath() string {
|
||||
return s.socketPath
|
||||
}
|
Reference in New Issue
Block a user