This commit is contained in:
2025-05-31 12:45:05 +03:00
parent a96ae1252c
commit b6a2671665
9 changed files with 992 additions and 54 deletions

View 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')
}

View File

@@ -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:')

View 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

View File

@@ -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}')
}
// Start a service
zinit_client.client.start('my_service')!
// Get service status
status := zinit_client.client.status('my_service')!
println('Service state: ${status.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()!
}
```
## 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()!
// 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')!
}
```
https://github.com/threefoldtech/zinit/blob/master/docs/protocol.md
### 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.

View 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)!
}

View 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}')
}
}
*/

View File

@@ -9,22 +9,23 @@ import json
@[params]
pub struct ZinitConfig {
path string = '/etc/zinit'
pathcmds string = '/etc/zinit/cmds'
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()!
}

View File

@@ -10,3 +10,8 @@ should return something like this:
now copy following
{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":286703868}

View File

@@ -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 {
@@ -22,7 +23,7 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
// Create a Unix domain socket client
// console.print_debug('Connecting to Unix socket at: $t.socket_path')
mut socket := unix.connect_stream(t.socket_path)!
// Ensure socket is always closed, even if there's an error
defer {
// Close the socket explicitly
@@ -35,23 +36,44 @@ 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')
socket.write_string(request + '\n')!
// println(18)
// Read the response in a single call with a larger buffer
mut res := []u8{len: 8192, cap: 8192}
n := socket.read(mut res)!
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 {
return error('Empty response received from server')