...
This commit is contained in:
126
examples/osal/zinit/zinit_openrpc_example.v
Normal file
126
examples/osal/zinit/zinit_openrpc_example.v
Normal file
@@ -0,0 +1,126 @@
|
||||
module main
|
||||
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
import json
|
||||
|
||||
fn main() {
|
||||
// Create a new Zinit client with the default socket path
|
||||
mut zinit_client := zinit.new_stateless(socket_path: '/tmp/zinit.sock')!
|
||||
|
||||
println('Connected to Zinit via OpenRPC')
|
||||
|
||||
// Example 1: Get the OpenRPC API specification
|
||||
println('\n=== Getting API Specification ===')
|
||||
api_spec := zinit_client.client.discover() or {
|
||||
println('Error getting API spec: ${err}')
|
||||
return
|
||||
}
|
||||
println('API Specification (first 100 chars): ${api_spec[..100]}...')
|
||||
|
||||
// Example 2: List all services
|
||||
println('\n=== Listing Services ===')
|
||||
service_list := zinit_client.client.list() or {
|
||||
println('Error listing services: ${err}')
|
||||
return
|
||||
}
|
||||
println('Services:')
|
||||
for name, state in service_list {
|
||||
println('- ${name}: ${state}')
|
||||
}
|
||||
|
||||
// Example 3: Get detailed status of a service (if any exist)
|
||||
if service_list.len > 0 {
|
||||
service_name := service_list.keys()[0]
|
||||
println('\n=== Getting Status for Service: ${service_name} ===')
|
||||
|
||||
status := zinit_client.client.status(service_name) or {
|
||||
println('Error getting status: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Status:')
|
||||
println('- Name: ${status.name}')
|
||||
println('- PID: ${status.pid}')
|
||||
println('- State: ${status.state}')
|
||||
println('- Target: ${status.target}')
|
||||
println('- Dependencies:')
|
||||
for dep_name, dep_state in status.after {
|
||||
println(' - ${dep_name}: ${dep_state}')
|
||||
}
|
||||
|
||||
// Example 4: Get service stats
|
||||
println('\n=== Getting Stats for Service: ${service_name} ===')
|
||||
stats := zinit_client.client.stats(service_name) or {
|
||||
println('Error getting stats: ${err}')
|
||||
println('Note: Stats are only available for running services')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Stats:')
|
||||
println('- Memory Usage: ${stats.memory_usage} bytes')
|
||||
println('- CPU Usage: ${stats.cpu_usage}%')
|
||||
if stats.children.len > 0 {
|
||||
println('- Child Processes:')
|
||||
for child in stats.children {
|
||||
println(' - PID: ${child.pid}, Memory: ${child.memory_usage} bytes, CPU: ${child.cpu_usage}%')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println('\nNo services found to query')
|
||||
}
|
||||
|
||||
// Example 5: Create a new service (commented out for safety)
|
||||
/*
|
||||
println('\n=== Creating a New Service ===')
|
||||
new_service_config := zinit.ServiceConfig{
|
||||
exec: '/bin/echo "Hello from Zinit"'
|
||||
oneshot: true
|
||||
after: []string{}
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'ENV_VAR': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
result := zinit_client.client.create_service('example_service', new_service_config) or {
|
||||
println('Error creating service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service created: ${result}')
|
||||
|
||||
// Start the service
|
||||
zinit_client.client.start('example_service') or {
|
||||
println('Error starting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service started')
|
||||
|
||||
// Get logs
|
||||
logs := zinit_client.client.get_logs('example_service') or {
|
||||
println('Error getting logs: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service logs:')
|
||||
for log in logs {
|
||||
println('- ${log}')
|
||||
}
|
||||
|
||||
// Delete the service when done
|
||||
zinit_client.client.stop('example_service') or {
|
||||
println('Error stopping service: ${err}')
|
||||
return
|
||||
}
|
||||
time.sleep(1 * time.second)
|
||||
zinit_client.client.forget('example_service') or {
|
||||
println('Error forgetting service: ${err}')
|
||||
return
|
||||
}
|
||||
zinit_client.client.delete_service('example_service') or {
|
||||
println('Error deleting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service deleted')
|
||||
*/
|
||||
|
||||
println('\nZinit OpenRPC client example completed')
|
||||
}
|
||||
@@ -50,11 +50,11 @@ if service_list.len > 0 {
|
||||
|
||||
// Create a request for service_status method with the service name as parameter
|
||||
// The parameter for service_status is a single string (service name)
|
||||
status_request := jsonrpc.new_request_generic('service_status', service_name)
|
||||
status_request := jsonrpc.new_request_generic('service_status', {"name":service_name})
|
||||
|
||||
// Send the request and receive a ServiceStatus object
|
||||
println('\nSending service_status request for service: $service_name')
|
||||
service_status := cl.send[string, ServiceStatus](status_request)!
|
||||
service_status := cl.send[map[string]string, ServiceStatus](status_request)!
|
||||
|
||||
// Display the service status details
|
||||
println('Service Status:')
|
||||
|
||||
224
lib/osal/zinit/implementation_plan.md
Normal file
224
lib/osal/zinit/implementation_plan.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Implementation Plan: Zinit OpenRPC Client Refactoring
|
||||
|
||||
## Current State Analysis
|
||||
- Multiple implementations (zinit.v, zinit_stateless.v, rpc.v, zprocess.v)
|
||||
- Inconsistent use of OpenRPC vs. direct filesystem operations
|
||||
- Duplication of functionality across multiple files
|
||||
- Lack of a unified approach
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Create a New Unified OpenRPC Client
|
||||
1. Create a new file `zinit_client.v` with a unified `ZinitClient` struct
|
||||
2. Implement all methods using the OpenRPC protocol exclusively
|
||||
3. Ensure the client handles all error cases properly
|
||||
4. Add comprehensive documentation for all methods
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Create New OpenRPC Client] --> B[Implement Core Client Methods]
|
||||
B --> C[Implement Service Management Methods]
|
||||
C --> D[Implement Configuration Management Methods]
|
||||
D --> E[Implement System Operations Methods]
|
||||
E --> F[Implement Logging Methods]
|
||||
```
|
||||
|
||||
### Phase 2: Refactor Existing Implementations
|
||||
1. Refactor `ZinitStateless` to use the new client for all operations
|
||||
2. Refactor `Zinit` to use the new client for all operations
|
||||
3. Refactor `ZProcess` to use the new client for all operations
|
||||
4. Update factory methods to use the new client
|
||||
5. Ensure backward compatibility by maintaining the same API/interface
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Refactor ZinitStateless] --> B[Refactor Zinit]
|
||||
B --> C[Refactor ZProcess]
|
||||
C --> D[Update Factory Methods]
|
||||
D --> E[Ensure Backward Compatibility]
|
||||
```
|
||||
|
||||
### Phase 3: Update Tests and Examples
|
||||
1. Update existing tests to use the new client
|
||||
2. Add new tests to cover all OpenRPC methods
|
||||
3. Update examples to demonstrate the new client
|
||||
4. Create new examples to showcase best practices
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Update Unit Tests] --> B[Update Integration Tests]
|
||||
B --> C[Update Examples]
|
||||
C --> D[Create New Examples]
|
||||
```
|
||||
|
||||
### Phase 4: Documentation and Cleanup
|
||||
1. Update the README with comprehensive documentation
|
||||
2. Add detailed API documentation for all methods
|
||||
3. Add usage examples for common scenarios
|
||||
4. Mark old implementations as deprecated (with a migration guide)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Update README] --> B[Add API Documentation]
|
||||
B --> C[Add Usage Examples]
|
||||
C --> D[Mark Deprecated Code]
|
||||
```
|
||||
|
||||
## Detailed Implementation Steps
|
||||
|
||||
### 1. Create New OpenRPC Client (zinit_client.v)
|
||||
|
||||
```v
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import json
|
||||
|
||||
// ZinitClient is a unified client for interacting with Zinit using OpenRPC
|
||||
pub struct ZinitClient {
|
||||
pub mut:
|
||||
rpc_client &jsonrpc.Client
|
||||
}
|
||||
|
||||
// new_client creates a new Zinit OpenRPC client
|
||||
pub fn new_client(socket_path string) ZinitClient {
|
||||
mut cl := jsonrpc.new_unix_socket_client(socket_path)
|
||||
return ZinitClient{
|
||||
rpc_client: cl
|
||||
}
|
||||
}
|
||||
|
||||
// Implement all OpenRPC methods...
|
||||
```
|
||||
|
||||
### 2. Refactor ZinitStateless (zinit_stateless.v)
|
||||
|
||||
```v
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[params]
|
||||
pub struct ZinitConfig {
|
||||
path string = '/etc/zinit'
|
||||
pathcmds string = '/etc/zinit/cmds'
|
||||
socket_path string = default_socket_path
|
||||
}
|
||||
|
||||
pub struct ZinitStateless {
|
||||
pub mut:
|
||||
client ZinitClient
|
||||
path pathlib.Path
|
||||
pathcmds pathlib.Path
|
||||
}
|
||||
|
||||
pub fn new_stateless(z ZinitConfig) !ZinitStateless {
|
||||
return ZinitStateless{
|
||||
client: new_client(z.socket_path)
|
||||
path: pathlib.get_dir(path: z.path, create: true)!
|
||||
pathcmds: pathlib.get_dir(path: z.pathcmds, create: true)!
|
||||
}
|
||||
}
|
||||
|
||||
// Refactor methods to use the OpenRPC client...
|
||||
```
|
||||
|
||||
### 3. Refactor Zinit (zinit.v)
|
||||
|
||||
```v
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[heap]
|
||||
pub struct Zinit {
|
||||
pub mut:
|
||||
processes map[string]ZProcess
|
||||
path pathlib.Path
|
||||
pathcmds pathlib.Path
|
||||
client ZinitClient
|
||||
}
|
||||
|
||||
// Refactor methods to use the OpenRPC client...
|
||||
```
|
||||
|
||||
### 4. Refactor ZProcess (zprocess.v)
|
||||
|
||||
```v
|
||||
module zinit
|
||||
|
||||
pub struct ZProcess {
|
||||
pub:
|
||||
name string = 'default'
|
||||
pub mut:
|
||||
cmd string
|
||||
cmd_stop string
|
||||
cmd_test string
|
||||
workdir string
|
||||
status ZProcessStatus
|
||||
pid int
|
||||
after []string
|
||||
env map[string]string
|
||||
oneshot bool
|
||||
start bool = true
|
||||
restart bool = true
|
||||
description string
|
||||
client &ZinitClient
|
||||
}
|
||||
|
||||
// Refactor methods to use the OpenRPC client...
|
||||
```
|
||||
|
||||
## Key Changes Required
|
||||
|
||||
1. **Replace Direct Filesystem Operations**:
|
||||
- Replace file creation/modification with `service_create` OpenRPC calls
|
||||
- Replace file deletion with `service_delete` OpenRPC calls
|
||||
- Replace file reading with `service_get` OpenRPC calls
|
||||
|
||||
2. **Replace Shell Commands**:
|
||||
- Replace `zinit list` shell commands with `service_list` OpenRPC calls
|
||||
- Replace `zinit status` shell commands with `service_status` OpenRPC calls
|
||||
- Replace `zinit log` shell commands with `stream_currentLogs` OpenRPC calls
|
||||
|
||||
3. **Unify Error Handling**:
|
||||
- Implement consistent error handling across all methods
|
||||
- Properly propagate OpenRPC error responses to the caller
|
||||
|
||||
4. **Maintain Backward Compatibility**:
|
||||
- Keep the same method signatures for public methods
|
||||
- Ensure the same behavior for all methods
|
||||
- Add deprecation notices for methods that will be removed in the future
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Unit Tests**:
|
||||
- Test each OpenRPC method individually
|
||||
- Test error handling for each method
|
||||
- Test with mock responses for predictable testing
|
||||
|
||||
2. **Integration Tests**:
|
||||
- Test with a real Zinit instance
|
||||
- Test the full lifecycle of services (create, start, status, stop, delete)
|
||||
- Test edge cases and error conditions
|
||||
|
||||
3. **Backward Compatibility Tests**:
|
||||
- Test existing code that uses the old implementations
|
||||
- Ensure no regressions in functionality
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
1. **README.md**:
|
||||
- Update with comprehensive documentation
|
||||
- Add examples for common use cases
|
||||
- Add migration guide for users of the old implementations
|
||||
|
||||
2. **API Documentation**:
|
||||
- Document all public methods
|
||||
- Document all structs and their fields
|
||||
- Document error conditions and how to handle them
|
||||
|
||||
3. **Examples**:
|
||||
- Update existing examples
|
||||
- Add new examples for common use cases
|
||||
- Add examples for error handling
|
||||
@@ -1,50 +1,155 @@
|
||||
# a sal to work with zinit
|
||||
# Zinit OpenRPC Client
|
||||
|
||||
Easy reliable way how to work with processes
|
||||
This module provides a V language client for interacting with Zinit process manager using the OpenRPC protocol over a Unix socket.
|
||||
|
||||
## Overview
|
||||
|
||||
## Example
|
||||
Zinit is a process manager that allows you to manage services on a system. This client provides a way to interact with Zinit using its JSON-RPC API, which follows the OpenRPC specification.
|
||||
|
||||
```golang
|
||||
## Features
|
||||
|
||||
- Full implementation of the Zinit OpenRPC API
|
||||
- Type-safe request and response handling
|
||||
- Support for all Zinit operations:
|
||||
- Service management (start, stop, monitor, forget)
|
||||
- Service status and statistics
|
||||
- System operations (shutdown, reboot)
|
||||
- Log retrieval
|
||||
- Service configuration management
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
|
||||
fn main() {
|
||||
do() or { panic(err) }
|
||||
// Create a new Zinit client with the default socket path
|
||||
mut zinit_client := zinit.new_stateless()!
|
||||
|
||||
// List all services
|
||||
service_list := zinit_client.client.list()!
|
||||
println('Services:')
|
||||
for name, state in service_list {
|
||||
println('- ${name}: ${state}')
|
||||
}
|
||||
|
||||
fn do() ! {
|
||||
mut z:=zinit.get()!
|
||||
|
||||
z.destroy()!
|
||||
|
||||
// name string @[required]
|
||||
// cmd string @[required]
|
||||
// cmd_file bool //if we wanna force to run it as a file which is given to bash -c (not just a cmd in zinit)
|
||||
// cmd_stop string
|
||||
// cmd_test string
|
||||
// test_file bool
|
||||
// after []string
|
||||
// env map[string]string
|
||||
// oneshot bool
|
||||
p:=z.new(
|
||||
name:"test"
|
||||
cmd:'/bin/bash'
|
||||
)!
|
||||
|
||||
output:=p.log()!
|
||||
println(output)
|
||||
|
||||
p.check()! //will check the process is up and running
|
||||
|
||||
p.stop()!
|
||||
// Start a service
|
||||
zinit_client.client.start('my_service')!
|
||||
|
||||
// Get service status
|
||||
status := zinit_client.client.status('my_service')!
|
||||
println('Service state: ${status.state}')
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## protocol defined in
|
||||
### Creating a New Service
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
|
||||
sal on top of https://github.com/threefoldtech/zinit/tree/master
|
||||
fn main() {
|
||||
mut zinit_client := zinit.new_stateless()!
|
||||
|
||||
https://github.com/threefoldtech/zinit/blob/master/docs/protocol.md
|
||||
// Define service configuration
|
||||
service_config := zinit.ServiceConfig{
|
||||
exec: '/usr/bin/my-program --option value'
|
||||
oneshot: false
|
||||
after: ['dependency1', 'dependency2']
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'ENV_VAR1': 'value1'
|
||||
'ENV_VAR2': 'value2'
|
||||
}
|
||||
shutdown_timeout: 30
|
||||
}
|
||||
|
||||
// Create the service
|
||||
zinit_client.client.create_service('my_service', service_config)!
|
||||
|
||||
// Monitor and start the service
|
||||
zinit_client.client.monitor('my_service')!
|
||||
zinit_client.client.start('my_service')!
|
||||
}
|
||||
```
|
||||
|
||||
### Getting Service Statistics
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
|
||||
fn main() {
|
||||
mut zinit_client := zinit.new_stateless()!
|
||||
|
||||
// Get service stats
|
||||
stats := zinit_client.client.stats('my_service')!
|
||||
|
||||
println('Memory usage: ${stats.memory_usage} bytes')
|
||||
println('CPU usage: ${stats.cpu_usage}%')
|
||||
|
||||
// Print child process stats
|
||||
for child in stats.children {
|
||||
println('Child PID: ${child.pid}, Memory: ${child.memory_usage} bytes')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving Logs
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
|
||||
fn main() {
|
||||
mut zinit_client := zinit.new_stateless()!
|
||||
|
||||
// Get logs for a specific service
|
||||
logs := zinit_client.client.get_logs('my_service')!
|
||||
for log in logs {
|
||||
println(log)
|
||||
}
|
||||
|
||||
// Get all logs
|
||||
all_logs := zinit_client.client.get_all_logs()!
|
||||
for log in all_logs {
|
||||
println(log)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Client Methods
|
||||
|
||||
- `discover()` - Returns the OpenRPC specification for the API
|
||||
- `list()` - Returns a map of service names to their current states
|
||||
- `status(name string)` - Returns detailed status information for a specific service
|
||||
- `start(name string)` - Starts a service
|
||||
- `stop(name string)` - Stops a service
|
||||
- `monitor(name string)` - Starts monitoring a service
|
||||
- `forget(name string)` - Stops monitoring a service
|
||||
- `kill(name string, signal string)` - Sends a signal to a running service
|
||||
- `shutdown()` - Stops all services and powers off the system
|
||||
- `reboot()` - Stops all services and reboots the system
|
||||
- `stats(name string)` - Returns memory and CPU usage statistics for a service
|
||||
- `get_logs(name string)` - Returns current logs from a specific service
|
||||
- `get_all_logs()` - Returns all current logs from zinit and monitored services
|
||||
- `create_service(name string, config ServiceConfig)` - Creates a new service configuration file
|
||||
- `delete_service(name string)` - Deletes a service configuration file
|
||||
- `get_service(name string)` - Gets a service configuration file
|
||||
- `start_http_server(address string)` - Starts an HTTP/RPC server at the specified address
|
||||
- `stop_http_server()` - Stops the HTTP/RPC server if running
|
||||
|
||||
### Data Structures
|
||||
|
||||
- `ServiceStatus` - Detailed status information for a service
|
||||
- `ServiceStats` - Memory and CPU usage statistics for a service
|
||||
- `ServiceConfig` - Configuration for creating a new service
|
||||
|
||||
## OpenRPC Specification
|
||||
|
||||
The full OpenRPC specification for the Zinit API is available in the `openrpc.json` file. This specification defines all the methods, parameters, and return types for the API.
|
||||
|
||||
## Example
|
||||
|
||||
See the `examples/osal/zinit/zinit_openrpc_example.v` file for a complete example of using the Zinit OpenRPC client.
|
||||
290
lib/osal/zinit/zinit_client.v
Normal file
290
lib/osal/zinit/zinit_client.v
Normal file
@@ -0,0 +1,290 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// Default socket path for Zinit
|
||||
pub const default_socket_path = '/tmp/zinit.sock'
|
||||
|
||||
// ZinitClient is a unified client for interacting with Zinit using OpenRPC
|
||||
pub struct ZinitClient {
|
||||
pub mut:
|
||||
rpc_client &jsonrpc.Client
|
||||
}
|
||||
|
||||
// new_client creates a new Zinit OpenRPC client
|
||||
// Parameters:
|
||||
// - socket_path: Path to the Zinit Unix socket (default: /tmp/zinit.sock)
|
||||
// Returns:
|
||||
// - A new ZinitClient instance
|
||||
pub fn new_client(socket_path string) ZinitClient {
|
||||
mut cl := jsonrpc.new_unix_socket_client(socket_path)
|
||||
return ZinitClient{
|
||||
rpc_client: cl
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRPCSpec represents the OpenRPC specification
|
||||
pub struct OpenRPCSpec {
|
||||
pub:
|
||||
openrpc string
|
||||
info OpenRPCInfo
|
||||
methods []OpenRPCMethod
|
||||
}
|
||||
|
||||
// OpenRPCInfo represents the info section of the OpenRPC specification
|
||||
pub struct OpenRPCInfo {
|
||||
pub:
|
||||
version string
|
||||
title string
|
||||
description string
|
||||
}
|
||||
|
||||
// OpenRPCMethod represents a method in the OpenRPC specification
|
||||
pub struct OpenRPCMethod {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// discover returns the OpenRPC specification for the API
|
||||
// Returns:
|
||||
// - A string representation of the OpenRPC specification
|
||||
pub fn (mut c ZinitClient) discover() !string {
|
||||
// Use a simpler approach - just get the raw response and return it as a string
|
||||
request := jsonrpc.new_request_generic('rpc.discover', []string{})
|
||||
|
||||
// Send the request and get the raw response
|
||||
raw_response := c.rpc_client.rpc_client.transport.send(request.encode(), jsonrpc.SendParams{
|
||||
timeout: 5 // Increase timeout to 5 seconds
|
||||
})!
|
||||
|
||||
// Extract just the result part from the response
|
||||
// This is a simplified approach to avoid full JSON parsing
|
||||
start_idx := raw_response.index('{"info":') or { return error('Invalid response format') }
|
||||
|
||||
// Return the raw JSON string
|
||||
return raw_response[start_idx..]
|
||||
}
|
||||
|
||||
// list returns a map of service names to their current states
|
||||
// Returns:
|
||||
// - A map where keys are service names and values are their states
|
||||
pub fn (mut c ZinitClient) list() !map[string]string {
|
||||
request := jsonrpc.new_request_generic('service_list', []string{})
|
||||
return c.rpc_client.send[[]string, map[string]string](request)!
|
||||
}
|
||||
|
||||
// ServiceStatus represents the detailed status of a service
|
||||
pub struct ServiceStatus {
|
||||
pub:
|
||||
name string
|
||||
pid int
|
||||
state string
|
||||
target string
|
||||
after map[string]string
|
||||
}
|
||||
|
||||
// status returns detailed status information for a specific service
|
||||
// Parameters:
|
||||
// - name: The name of the service to get status for
|
||||
// Returns:
|
||||
// - A ServiceStatus struct containing detailed status information
|
||||
pub fn (mut c ZinitClient) status(name string) !ServiceStatus {
|
||||
request := jsonrpc.new_request_generic('service_status', name)
|
||||
return c.rpc_client.send[string, ServiceStatus](request)!
|
||||
}
|
||||
|
||||
// EmptyResponse represents an empty response from the API
|
||||
pub struct EmptyResponse {}
|
||||
|
||||
// start starts a service
|
||||
// Parameters:
|
||||
// - name: The name of the service to start
|
||||
pub fn (mut c ZinitClient) start(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_start', name)
|
||||
c.rpc_client.send[string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// stop stops a service
|
||||
// Parameters:
|
||||
// - name: The name of the service to stop
|
||||
pub fn (mut c ZinitClient) stop(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_stop', name)
|
||||
c.rpc_client.send[string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// monitor starts monitoring a service
|
||||
// Parameters:
|
||||
// - name: The name of the service to monitor
|
||||
pub fn (mut c ZinitClient) monitor(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_monitor', name)
|
||||
c.rpc_client.send[string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// forget stops monitoring a service
|
||||
// Parameters:
|
||||
// - name: The name of the service to forget
|
||||
pub fn (mut c ZinitClient) forget(name string) ! {
|
||||
request := jsonrpc.new_request_generic('service_forget', name)
|
||||
c.rpc_client.send[string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// KillParams represents the parameters for the kill method
|
||||
pub struct KillParams {
|
||||
pub:
|
||||
name string
|
||||
signal string
|
||||
}
|
||||
|
||||
// kill sends a signal to a running service
|
||||
// Parameters:
|
||||
// - name: The name of the service to send the signal to
|
||||
// - signal: The signal to send (e.g., SIGTERM, SIGKILL)
|
||||
pub fn (mut c ZinitClient) kill(name string, signal string) ! {
|
||||
params := KillParams{
|
||||
name: name
|
||||
signal: signal
|
||||
}
|
||||
|
||||
request := jsonrpc.new_request_generic('service_kill', params)
|
||||
c.rpc_client.send[KillParams, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// shutdown stops all services and powers off the system
|
||||
pub fn (mut c ZinitClient) shutdown() ! {
|
||||
request := jsonrpc.new_request_generic('system_shutdown', []string{})
|
||||
c.rpc_client.send[[]string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// reboot stops all services and reboots the system
|
||||
pub fn (mut c ZinitClient) reboot() ! {
|
||||
request := jsonrpc.new_request_generic('system_reboot', []string{})
|
||||
c.rpc_client.send[[]string, EmptyResponse](request)!
|
||||
}
|
||||
|
||||
// ServiceStats represents memory and CPU usage statistics for a service
|
||||
pub struct ServiceStats {
|
||||
pub:
|
||||
name string
|
||||
pid int
|
||||
memory_usage i64
|
||||
cpu_usage f64
|
||||
children []ChildProcessStats
|
||||
}
|
||||
|
||||
// ChildProcessStats represents statistics for a child process
|
||||
pub struct ChildProcessStats {
|
||||
pub:
|
||||
pid int
|
||||
memory_usage i64
|
||||
cpu_usage f64
|
||||
}
|
||||
|
||||
// stats returns memory and CPU usage statistics for a service
|
||||
// Parameters:
|
||||
// - name: The name of the service to get stats for
|
||||
// Returns:
|
||||
// - A ServiceStats struct containing memory and CPU usage statistics
|
||||
pub fn (mut c ZinitClient) stats(name string) !ServiceStats {
|
||||
request := jsonrpc.new_request_generic('service_stats', name)
|
||||
return c.rpc_client.send[string, ServiceStats](request)!
|
||||
}
|
||||
|
||||
// get_logs returns current logs from a specific service
|
||||
// Parameters:
|
||||
// - name: The name of the service to get logs for
|
||||
// Returns:
|
||||
// - An array of log strings
|
||||
pub fn (mut c ZinitClient) get_logs(name string) ![]string {
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', name)
|
||||
return c.rpc_client.send[string, []string](request)!
|
||||
}
|
||||
|
||||
// get_all_logs returns all current logs from zinit and monitored services
|
||||
// Returns:
|
||||
// - An array of log strings
|
||||
pub fn (mut c ZinitClient) get_all_logs() ![]string {
|
||||
request := jsonrpc.new_request_generic('stream_currentLogs', []string{})
|
||||
return c.rpc_client.send[[]string, []string](request)!
|
||||
}
|
||||
|
||||
// ServiceConfig represents the configuration for a service
|
||||
pub struct ServiceConfig {
|
||||
pub:
|
||||
exec string
|
||||
oneshot bool
|
||||
after []string
|
||||
log string
|
||||
env map[string]string
|
||||
shutdown_timeout int
|
||||
}
|
||||
|
||||
// CreateServiceParams represents the parameters for the create_service method
|
||||
pub struct CreateServiceParams {
|
||||
pub:
|
||||
name string
|
||||
content ServiceConfig
|
||||
}
|
||||
|
||||
// create_service creates a new service configuration file
|
||||
// Parameters:
|
||||
// - name: The name of the service to create
|
||||
// - config: The service configuration
|
||||
// Returns:
|
||||
// - A string indicating the result of the operation
|
||||
pub fn (mut c ZinitClient) create_service(name string, config ServiceConfig) !string {
|
||||
params := CreateServiceParams{
|
||||
name: name
|
||||
content: config
|
||||
}
|
||||
|
||||
request := jsonrpc.new_request_generic('service_create', params)
|
||||
return c.rpc_client.send[CreateServiceParams, string](request)!
|
||||
}
|
||||
|
||||
// delete_service deletes a service configuration file
|
||||
// Parameters:
|
||||
// - name: The name of the service to delete
|
||||
// Returns:
|
||||
// - A string indicating the result of the operation
|
||||
pub fn (mut c ZinitClient) delete_service(name string) !string {
|
||||
request := jsonrpc.new_request_generic('service_delete', name)
|
||||
return c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
// ServiceConfigResponse represents the response from get_service
|
||||
pub struct ServiceConfigResponse {
|
||||
pub:
|
||||
exec string
|
||||
oneshot bool
|
||||
after []string
|
||||
log string
|
||||
env map[string]string
|
||||
shutdown_timeout int
|
||||
}
|
||||
|
||||
// get_service gets a service configuration file
|
||||
// Parameters:
|
||||
// - name: The name of the service to get
|
||||
// Returns:
|
||||
// - The service configuration
|
||||
pub fn (mut c ZinitClient) get_service(name string) !ServiceConfigResponse {
|
||||
request := jsonrpc.new_request_generic('service_get', name)
|
||||
return c.rpc_client.send[string, ServiceConfigResponse](request)!
|
||||
}
|
||||
|
||||
// start_http_server starts an HTTP/RPC server at the specified address
|
||||
// Parameters:
|
||||
// - address: The network address to bind the server to (e.g., '127.0.0.1:8080')
|
||||
// Returns:
|
||||
// - A string indicating the result of the operation
|
||||
pub fn (mut c ZinitClient) start_http_server(address string) !string {
|
||||
request := jsonrpc.new_request_generic('system_start_http_server', address)
|
||||
return c.rpc_client.send[string, string](request)!
|
||||
}
|
||||
|
||||
// stop_http_server stops the HTTP/RPC server if running
|
||||
pub fn (mut c ZinitClient) stop_http_server() ! {
|
||||
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
|
||||
c.rpc_client.send[[]string, EmptyResponse](request)!
|
||||
}
|
||||
165
lib/osal/zinit/zinit_openrpc_test.v
Normal file
165
lib/osal/zinit/zinit_openrpc_test.v
Normal file
@@ -0,0 +1,165 @@
|
||||
module zinit
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import os
|
||||
import rand
|
||||
import time
|
||||
|
||||
// These tests require a running Zinit instance with the Unix socket at /tmp/zinit.sock
|
||||
// If Zinit is not running, the tests will be skipped
|
||||
|
||||
fn test_client_creation() {
|
||||
if !os.exists('/tmp/zinit.sock') {
|
||||
println('Skipping test: Zinit socket not found at /tmp/zinit.sock')
|
||||
return
|
||||
}
|
||||
|
||||
client := new_client('/tmp/zinit.sock')
|
||||
assert client.rpc_client != unsafe { nil }
|
||||
}
|
||||
|
||||
fn test_service_list() {
|
||||
if !os.exists('/tmp/zinit.sock') {
|
||||
println('Skipping test: Zinit socket not found at /tmp/zinit.sock')
|
||||
return
|
||||
}
|
||||
|
||||
mut client := new_client('/tmp/zinit.sock')
|
||||
services := client.list() or {
|
||||
assert false, 'Failed to list services: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Just verify we got a map, even if it's empty
|
||||
assert typeof(services).name == 'map[string]string'
|
||||
println('Found ${services.len} services')
|
||||
}
|
||||
|
||||
fn test_discover() {
|
||||
if !os.exists('/tmp/zinit.sock') {
|
||||
println('Skipping test: Zinit socket not found at /tmp/zinit.sock')
|
||||
return
|
||||
}
|
||||
|
||||
mut client := new_client('/tmp/zinit.sock')
|
||||
spec := client.discover() or {
|
||||
assert false, 'Failed to get OpenRPC spec: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Verify we got a non-empty string
|
||||
assert spec.len > 0
|
||||
assert spec.contains('"openrpc"')
|
||||
assert spec.contains('"methods"')
|
||||
}
|
||||
|
||||
fn test_stateless_client() {
|
||||
if !os.exists('/tmp/zinit.sock') {
|
||||
println('Skipping test: Zinit socket not found at /tmp/zinit.sock')
|
||||
return
|
||||
}
|
||||
|
||||
// Create temporary directories for testing
|
||||
temp_dir := os.temp_dir()
|
||||
path := os.join_path(temp_dir, 'zinit_test')
|
||||
pathcmds := os.join_path(temp_dir, 'zinit_test_cmds')
|
||||
|
||||
// Create the directories
|
||||
os.mkdir_all(path) or {
|
||||
assert false, 'Failed to create test directory: ${err}'
|
||||
return
|
||||
}
|
||||
os.mkdir_all(pathcmds) or {
|
||||
assert false, 'Failed to create test commands directory: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up after the test
|
||||
defer {
|
||||
os.rmdir_all(path) or {}
|
||||
os.rmdir_all(pathcmds) or {}
|
||||
}
|
||||
|
||||
mut zinit_client := new_stateless(
|
||||
socket_path: '/tmp/zinit.sock'
|
||||
path: path
|
||||
pathcmds: pathcmds
|
||||
) or {
|
||||
assert false, 'Failed to create stateless client: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Test the names method which uses the client
|
||||
names := zinit_client.names() or {
|
||||
assert false, 'Failed to get service names: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
assert typeof(names).name == '[]string'
|
||||
}
|
||||
|
||||
// This test creates a test service, starts it, checks its status, and then cleans up
|
||||
// It's commented out by default to avoid modifying the system
|
||||
/*
|
||||
fn test_service_lifecycle() {
|
||||
if !os.exists('/tmp/zinit.sock') {
|
||||
println('Skipping test: Zinit socket not found at /tmp/zinit.sock')
|
||||
return
|
||||
}
|
||||
|
||||
service_name := 'test_service_${rand.int_in_range(1000, 9999)}'
|
||||
mut client := new_client('/tmp/zinit.sock')
|
||||
|
||||
// Create service config
|
||||
config := ServiceConfig{
|
||||
exec: '/bin/echo "Test service running"'
|
||||
oneshot: true
|
||||
after: []string{}
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'TEST_VAR': 'test_value'
|
||||
}
|
||||
}
|
||||
|
||||
// Create the service
|
||||
client.create_service(service_name, config) or {
|
||||
assert false, 'Failed to create service: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Monitor the service
|
||||
client.monitor(service_name) or {
|
||||
assert false, 'Failed to monitor service: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Start the service
|
||||
client.start(service_name) or {
|
||||
assert false, 'Failed to start service: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Check service status
|
||||
status := client.status(service_name) or {
|
||||
assert false, 'Failed to get service status: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
assert status.name == service_name
|
||||
|
||||
// Clean up
|
||||
client.stop(service_name) or {
|
||||
println('Warning: Failed to stop service: ${err}')
|
||||
}
|
||||
|
||||
time.sleep(1 * time.second)
|
||||
|
||||
client.forget(service_name) or {
|
||||
println('Warning: Failed to forget service: ${err}')
|
||||
}
|
||||
|
||||
client.delete_service(service_name) or {
|
||||
println('Warning: Failed to delete service: ${err}')
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -11,20 +11,21 @@ import json
|
||||
pub struct ZinitConfig {
|
||||
path string = '/etc/zinit'
|
||||
pathcmds string = '/etc/zinit/cmds'
|
||||
socket_path string = default_socket_path
|
||||
}
|
||||
|
||||
pub struct ZinitStateless {
|
||||
pub mut:
|
||||
client Client
|
||||
client ZinitClient
|
||||
path pathlib.Path
|
||||
pathcmds pathlib.Path
|
||||
}
|
||||
|
||||
pub fn new_stateless(z ZinitConfig) !ZinitStateless {
|
||||
return ZinitStateless{
|
||||
client: new_rpc_client()
|
||||
path: pathlib.get_dir(path: '/etc/zinit', create: true)!
|
||||
pathcmds: pathlib.get_dir(path: '/etc/zinit/cmds', create: true)!
|
||||
client: new_client(z.socket_path)
|
||||
path: pathlib.get_dir(path: z.path, create: true)!
|
||||
pathcmds: pathlib.get_dir(path: z.pathcmds, create: true)!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ fn (mut zinit ZinitStateless) cmd_write(name string, cmd string, cat string, env
|
||||
return '/bin/bash -c ${pathcmd.path}'
|
||||
}
|
||||
|
||||
pub fn (zinit ZinitStateless) exists(name string) !bool {
|
||||
pub fn (mut zinit ZinitStateless) exists(name string) !bool {
|
||||
return name in zinit.client.list()!
|
||||
}
|
||||
|
||||
|
||||
@@ -10,3 +10,8 @@ should return something like this:
|
||||
|
||||
|
||||
|
||||
now copy following
|
||||
{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":286703868}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ module jsonrpc
|
||||
import net.unix
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import net
|
||||
|
||||
// UnixSocketTransport implements the IRPCTransportClient interface for Unix domain sockets
|
||||
struct UnixSocketTransport {
|
||||
@@ -35,8 +36,9 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
|
||||
if params.timeout > 0 {
|
||||
socket.set_read_timeout(params.timeout * time.second)
|
||||
socket.set_write_timeout(params.timeout * time.second)
|
||||
// console.print_debug('Set socket timeout to ${params.timeout} seconds')
|
||||
console.print_debug('Set socket timeout to ${params.timeout} seconds')
|
||||
}
|
||||
net.set_blocking(socket.sock.handle,false) !
|
||||
|
||||
// Send the request
|
||||
// console.print_debug('Sending request: $request')
|
||||
@@ -44,13 +46,33 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
|
||||
// println(18)
|
||||
|
||||
// Read the response in a single call with a larger buffer
|
||||
mut res := []u8{len: 8192, cap: 8192}
|
||||
|
||||
mut res_total := []u8
|
||||
for {
|
||||
// console.print_debug('Reading response from socket...')
|
||||
// Read up to 64000 bytes
|
||||
mut res := []u8{len: 64000, cap: 64000}
|
||||
n := socket.read(mut res)!
|
||||
console.print_debug('Read ${n} bytes from socket')
|
||||
if n == 0 {
|
||||
console.print_debug('No more data to read, breaking loop')
|
||||
break
|
||||
}
|
||||
// Append the newly read data to the total response
|
||||
res_total << res[..n]
|
||||
if n < 8192{
|
||||
console.print_debug('No more data to read, breaking loop after ${n} bytes')
|
||||
break
|
||||
}
|
||||
}
|
||||
println(res_total.bytestr().trim_space())
|
||||
|
||||
|
||||
// println(19)
|
||||
|
||||
// Convert response to string and trim whitespace
|
||||
mut response := res[..n].bytestr().trim_space()
|
||||
// console.print_debug('Received ${n} bytes')
|
||||
mut response := res_total.bytestr().trim_space()
|
||||
console.print_debug('Received ${response.len} bytes')
|
||||
|
||||
// Basic validation
|
||||
if response.len == 0 {
|
||||
|
||||
Reference in New Issue
Block a user