...
This commit is contained in:
85
pkg/processmanager/examples/openrpc/README.md
Normal file
85
pkg/processmanager/examples/openrpc/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Process Manager OpenRPC Example
|
||||
|
||||
This example demonstrates how to use the Process Manager OpenRPC interface to interact with the process manager. It provides a complete working example of both server and client implementations, with a mock process manager for testing purposes.
|
||||
|
||||
## Overview
|
||||
|
||||
The example includes:
|
||||
|
||||
1. A server that exposes the process manager functionality via OpenRPC over a Unix socket
|
||||
2. A client that connects to the server and performs various operations
|
||||
3. A mock process manager implementation for testing without requiring actual processes
|
||||
|
||||
## Structure
|
||||
|
||||
- `main.go`: The main entry point that runs both the server and client in the same process
|
||||
- `mock_processmanager.go`: A mock implementation of the `ProcessManagerInterface` that simulates process operations
|
||||
- `example_client.go`: Client implementation demonstrating how to use the OpenRPC client to interact with the process manager
|
||||
|
||||
## How to Run
|
||||
|
||||
```bash
|
||||
# From the heroagent root directory
|
||||
go run ./pkg/processmanager/examples/openrpc
|
||||
```
|
||||
|
||||
The example will:
|
||||
1. Start a server using a Unix socket at `/tmp/process-manager.sock`
|
||||
2. Run a series of client operations against the server
|
||||
3. Display the results of each operation
|
||||
4. Clean up and shut down when complete
|
||||
|
||||
## What It Demonstrates
|
||||
|
||||
This example shows:
|
||||
|
||||
1. How to initialize and start an OpenRPC server for the process manager
|
||||
2. How to create and use an OpenRPC client to interact with the process manager
|
||||
3. How to perform common operations like:
|
||||
- Starting a process (`StartProcess`)
|
||||
- Getting process status (`GetProcessStatus`)
|
||||
- Listing all processes (`ListProcesses`)
|
||||
- Getting process logs (`GetProcessLogs`)
|
||||
- Restarting a process (`RestartProcess`)
|
||||
- Stopping a process (`StopProcess`)
|
||||
- Deleting a process (`DeleteProcess`)
|
||||
4. How to handle the response types returned by each operation
|
||||
5. Proper error handling for RPC operations
|
||||
|
||||
## Notes
|
||||
|
||||
- This example uses a mock process manager implementation for demonstration purposes
|
||||
- In a real application, you would use the actual process manager implementation
|
||||
- The server and client run in the same process for simplicity, but they could be in separate processes
|
||||
- The Unix socket communication can be replaced with other transport mechanisms if needed
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Server
|
||||
|
||||
The server is implemented using the `openrpc.Server` type, which wraps the OpenRPC manager and Unix socket server. It:
|
||||
|
||||
1. Creates a Unix socket at the specified path
|
||||
2. Registers handlers for each RPC method
|
||||
3. Authenticates requests using a secret
|
||||
4. Marshals/unmarshals JSON-RPC requests and responses
|
||||
|
||||
### Client
|
||||
|
||||
The client is implemented using the `openrpc.Client` type, which provides methods for each operation. It:
|
||||
|
||||
1. Connects to the Unix socket
|
||||
2. Sends JSON-RPC requests with the appropriate parameters
|
||||
3. Handles authentication using the secret
|
||||
4. Parses the responses into appropriate result types
|
||||
|
||||
### Mock Process Manager
|
||||
|
||||
The mock process manager implements the `interfaces.ProcessManagerInterface` and simulates:
|
||||
|
||||
1. Process creation and management
|
||||
2. Status tracking
|
||||
3. Log collection
|
||||
4. Error handling
|
||||
|
||||
This allows testing the OpenRPC interface without requiring actual processes to be run.
|
149
pkg/processmanager/examples/openrpc/example_client.go
Normal file
149
pkg/processmanager/examples/openrpc/example_client.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
)
|
||||
|
||||
// RunClientExample runs a complete example of using the process manager OpenRPC client
|
||||
func RunClientExample(socketPath, secret string) error {
|
||||
// Create a new client
|
||||
client := openrpc.NewClient(socketPath, secret)
|
||||
|
||||
log.Println("🚀 Starting example process...")
|
||||
// Start a process
|
||||
result, err := client.StartProcess("example-process", "sleep 60", true, 0, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start process: %w", err)
|
||||
}
|
||||
log.Printf("Start result: success=%v, message=%s, PID=%d", result.Success, result.Message, result.PID)
|
||||
|
||||
// Wait a bit for the process to start
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
log.Println("📊 Getting process status...")
|
||||
// Get the process status
|
||||
status, err := client.GetProcessStatus("example-process", "json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get process status: %w", err)
|
||||
}
|
||||
printProcessStatus(status.(interfaces.ProcessStatus))
|
||||
|
||||
log.Println("📋 Listing all processes...")
|
||||
// List all processes
|
||||
processList, err := client.ListProcesses("json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list processes: %w", err)
|
||||
}
|
||||
|
||||
// For simplicity in this example, just log that we got a response
|
||||
log.Printf("Got process list response: %T", processList)
|
||||
|
||||
// Try to handle the response in a more robust way
|
||||
switch v := processList.(type) {
|
||||
case []interface{}:
|
||||
log.Printf("Found %d processes", len(v))
|
||||
for i, p := range v {
|
||||
log.Printf("Process %d: %T", i, p)
|
||||
if processMap, ok := p.(map[string]interface{}); ok {
|
||||
log.Printf(" Name: %v", processMap["name"])
|
||||
log.Printf(" Command: %v", processMap["command"])
|
||||
log.Printf(" Status: %v", processMap["status"])
|
||||
}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
log.Printf("Process list is a map with %d entries", len(v))
|
||||
for k, val := range v {
|
||||
log.Printf(" %s: %T", k, val)
|
||||
}
|
||||
default:
|
||||
log.Printf("Process list is of unexpected type: %T", processList)
|
||||
}
|
||||
|
||||
log.Println("📜 Getting process logs...")
|
||||
// Get process logs
|
||||
logResult, err := client.GetProcessLogs("example-process", 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get process logs: %w", err)
|
||||
}
|
||||
log.Printf("Process logs: success=%v, message=%s", logResult.Success, logResult.Message)
|
||||
log.Printf("Logs:\n%s", logResult.Logs)
|
||||
|
||||
log.Println("🔄 Restarting process...")
|
||||
// Restart the process
|
||||
restartResult, err := client.RestartProcess("example-process")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart process: %w", err)
|
||||
}
|
||||
log.Printf("Restart result: success=%v, message=%s, PID=%d", restartResult.Success, restartResult.Message, restartResult.PID)
|
||||
|
||||
// Wait a bit for the process to restart
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Get the process status after restart
|
||||
status, err = client.GetProcessStatus("example-process", "json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get process status after restart: %w", err)
|
||||
}
|
||||
log.Println("Process status after restart:")
|
||||
printProcessStatus(status.(interfaces.ProcessStatus))
|
||||
|
||||
log.Println("⏹️ Stopping process...")
|
||||
// Stop the process
|
||||
stopResult, err := client.StopProcess("example-process")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process: %w", err)
|
||||
}
|
||||
log.Printf("Stop result: success=%v, message=%s", stopResult.Success, stopResult.Message)
|
||||
|
||||
// Wait a bit for the process to stop
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Get the process status after stop
|
||||
status, err = client.GetProcessStatus("example-process", "json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get process status after stop: %w", err)
|
||||
}
|
||||
log.Println("Process status after stop:")
|
||||
printProcessStatus(status.(interfaces.ProcessStatus))
|
||||
|
||||
log.Println("🗑️ Deleting process...")
|
||||
// Delete the process
|
||||
deleteResult, err := client.DeleteProcess("example-process")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete process: %w", err)
|
||||
}
|
||||
log.Printf("Delete result: success=%v, message=%s", deleteResult.Success, deleteResult.Message)
|
||||
|
||||
// Try to get the process status after delete (should fail)
|
||||
_, err = client.GetProcessStatus("example-process", "json")
|
||||
if err != nil {
|
||||
log.Printf("Expected error after deletion: %v", err)
|
||||
} else {
|
||||
return fmt.Errorf("process still exists after deletion")
|
||||
}
|
||||
|
||||
log.Println("✅ Example completed successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// printProcessStatus prints the status of a process
|
||||
func printProcessStatus(status interfaces.ProcessStatus) {
|
||||
log.Printf("Process: %s", status.Name)
|
||||
log.Printf(" Command: %s", status.Command)
|
||||
log.Printf(" Status: %s", status.Status)
|
||||
log.Printf(" PID: %d", status.PID)
|
||||
if status.CPUPercent > 0 {
|
||||
log.Printf(" CPU: %.2f%%", status.CPUPercent)
|
||||
}
|
||||
if status.MemoryMB > 0 {
|
||||
log.Printf(" Memory: %.2f MB", status.MemoryMB)
|
||||
}
|
||||
if !status.StartTime.IsZero() {
|
||||
log.Printf(" Started: %s", status.StartTime.Format(time.RFC3339))
|
||||
}
|
||||
}
|
75
pkg/processmanager/examples/openrpc/main.go
Normal file
75
pkg/processmanager/examples/openrpc/main.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Starting Process Manager OpenRPC Example")
|
||||
|
||||
// Use /tmp directory for the socket as it's more reliable for Unix sockets
|
||||
// Define the socket path
|
||||
socketPath := "/tmp/process-manager.sock"
|
||||
// Remove the socket file if it already exists
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
if err := os.Remove(socketPath); err != nil {
|
||||
log.Fatalf("Failed to remove existing socket file: %v", err)
|
||||
}
|
||||
}
|
||||
log.Printf("Using socket path: %s", socketPath)
|
||||
|
||||
// Create a mock process manager
|
||||
mockPM := NewMockProcessManager()
|
||||
|
||||
// Create and start the server
|
||||
server, err := openrpc.NewServer(mockPM, socketPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create server: %v", err)
|
||||
}
|
||||
|
||||
// Start the server
|
||||
err = server.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
log.Println("Server started successfully")
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
// Wait a bit for the server to start
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Run the client example in a goroutine
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
err := RunClientExample(socketPath, mockPM.GetSecret())
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
// Wait for the client to finish or for a signal
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
log.Printf("Client example failed: %v", err)
|
||||
}
|
||||
case sig := <-sigChan:
|
||||
log.Printf("Received signal: %v", sig)
|
||||
}
|
||||
|
||||
// Stop the server
|
||||
log.Println("Stopping server...")
|
||||
err = server.Stop()
|
||||
if err != nil {
|
||||
log.Printf("Failed to stop server: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Example completed")
|
||||
}
|
195
pkg/processmanager/examples/openrpc/mock_processmanager.go
Normal file
195
pkg/processmanager/examples/openrpc/mock_processmanager.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
|
||||
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
|
||||
)
|
||||
|
||||
// MockProcessManager implements the interfaces.ProcessManagerInterface for testing purposes
|
||||
type MockProcessManager struct {
|
||||
processes map[string]*processmanager.ProcessInfo
|
||||
logs map[string][]string
|
||||
mutex sync.RWMutex
|
||||
secret string
|
||||
}
|
||||
|
||||
// Ensure MockProcessManager implements interfaces.ProcessManagerInterface
|
||||
var _ interfaces.ProcessManagerInterface = (*MockProcessManager)(nil)
|
||||
|
||||
// NewMockProcessManager creates a new mock process manager
|
||||
func NewMockProcessManager() *MockProcessManager {
|
||||
return &MockProcessManager{
|
||||
processes: make(map[string]*processmanager.ProcessInfo),
|
||||
logs: make(map[string][]string),
|
||||
secret: "mock-secret",
|
||||
}
|
||||
}
|
||||
|
||||
// StartProcess starts a new process
|
||||
func (m *MockProcessManager) StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.processes[name]; exists {
|
||||
return fmt.Errorf("process %s already exists", name)
|
||||
}
|
||||
|
||||
process := &processmanager.ProcessInfo{
|
||||
Name: name,
|
||||
Command: command,
|
||||
PID: 12345, // Mock PID
|
||||
Status: processmanager.ProcessStatusRunning,
|
||||
CPUPercent: 0.5,
|
||||
MemoryMB: 10.0,
|
||||
StartTime: time.Now(),
|
||||
LogEnabled: logEnabled,
|
||||
Cron: cron,
|
||||
JobID: jobID,
|
||||
Deadline: deadline,
|
||||
}
|
||||
|
||||
m.processes[name] = process
|
||||
m.logs[name] = []string{
|
||||
fmt.Sprintf("[%s] Process started: %s", time.Now().Format(time.RFC3339), command),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopProcess stops a running process
|
||||
func (m *MockProcessManager) StopProcess(name string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
process, exists := m.processes[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("process %s does not exist", name)
|
||||
}
|
||||
|
||||
if process.Status != processmanager.ProcessStatusRunning {
|
||||
return fmt.Errorf("process %s is not running", name)
|
||||
}
|
||||
|
||||
process.Status = processmanager.ProcessStatusStopped
|
||||
|
||||
m.logs[name] = append(m.logs[name], fmt.Sprintf("[%s] Process stopped", time.Now().Format(time.RFC3339)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartProcess restarts a process
|
||||
func (m *MockProcessManager) RestartProcess(name string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
process, exists := m.processes[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("process %s does not exist", name)
|
||||
}
|
||||
|
||||
process.Status = processmanager.ProcessStatusRunning
|
||||
process.StartTime = time.Now()
|
||||
|
||||
m.logs[name] = append(m.logs[name], fmt.Sprintf("[%s] Process restarted", time.Now().Format(time.RFC3339)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProcess deletes a process
|
||||
func (m *MockProcessManager) DeleteProcess(name string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.processes[name]; !exists {
|
||||
return fmt.Errorf("process %s does not exist", name)
|
||||
}
|
||||
|
||||
delete(m.processes, name)
|
||||
delete(m.logs, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProcessStatus gets the status of a process
|
||||
func (m *MockProcessManager) GetProcessStatus(name string) (*processmanager.ProcessInfo, error) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
process, exists := m.processes[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("process %s does not exist", name)
|
||||
}
|
||||
|
||||
// Return a copy to avoid race conditions
|
||||
return &processmanager.ProcessInfo{
|
||||
Name: process.Name,
|
||||
Command: process.Command,
|
||||
PID: process.PID,
|
||||
Status: process.Status,
|
||||
CPUPercent: process.CPUPercent,
|
||||
MemoryMB: process.MemoryMB,
|
||||
StartTime: process.StartTime,
|
||||
LogEnabled: process.LogEnabled,
|
||||
Cron: process.Cron,
|
||||
JobID: process.JobID,
|
||||
Deadline: process.Deadline,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListProcesses lists all processes
|
||||
func (m *MockProcessManager) ListProcesses() []*processmanager.ProcessInfo {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
processes := make([]*processmanager.ProcessInfo, 0, len(m.processes))
|
||||
for _, process := range m.processes {
|
||||
// Create a copy to avoid race conditions
|
||||
processes = append(processes, &processmanager.ProcessInfo{
|
||||
Name: process.Name,
|
||||
Command: process.Command,
|
||||
PID: process.PID,
|
||||
Status: process.Status,
|
||||
CPUPercent: process.CPUPercent,
|
||||
MemoryMB: process.MemoryMB,
|
||||
StartTime: process.StartTime,
|
||||
LogEnabled: process.LogEnabled,
|
||||
Cron: process.Cron,
|
||||
JobID: process.JobID,
|
||||
Deadline: process.Deadline,
|
||||
})
|
||||
}
|
||||
|
||||
return processes
|
||||
}
|
||||
|
||||
// GetProcessLogs gets the logs for a process
|
||||
func (m *MockProcessManager) GetProcessLogs(name string, maxLines int) (string, error) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
logs, exists := m.logs[name]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("logs for process %s do not exist", name)
|
||||
}
|
||||
|
||||
if maxLines <= 0 || maxLines > len(logs) {
|
||||
return strings.Join(logs, "\n"), nil
|
||||
}
|
||||
|
||||
return strings.Join(logs[len(logs)-maxLines:], "\n"), nil
|
||||
}
|
||||
|
||||
// SetLogsBasePath sets the base path for logs (mock implementation does nothing)
|
||||
func (m *MockProcessManager) SetLogsBasePath(path string) {
|
||||
// No-op for mock
|
||||
}
|
||||
|
||||
// GetSecret returns the authentication secret
|
||||
func (m *MockProcessManager) GetSecret() string {
|
||||
return m.secret
|
||||
}
|
Reference in New Issue
Block a user