diff --git a/go.mod b/go.mod index 62c70b0a..3243b4bc 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/threefoldtech/0-fs v1.3.1-0.20240424140157-b488dfedcc56 github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2 + github.com/threefoldtech/tfgrid-sdk-go/messenger v0.0.0-00010101000000-000000000000 github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go v0.16.1-0.20241229121208-76ac3fea5e67 github.com/threefoldtech/zbus v1.0.1 github.com/tyler-smith/go-bip39 v1.1.0 diff --git a/go.sum b/go.sum index 5a630d10..3994d45d 100644 --- a/go.sum +++ b/go.sum @@ -562,8 +562,6 @@ github.com/stripe/safesql v0.2.0/go.mod h1:q7b2n0JmzM1mVGfcYpanfVb2j23cXZeWFxcIL github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/threefoldtech/0-fs v1.3.1-0.20240424140157-b488dfedcc56 h1:uWd8JfE8N3IM6Zw/LMr0+sRJl+G0YgqQmNDP8xXR0vw= github.com/threefoldtech/0-fs v1.3.1-0.20240424140157-b488dfedcc56/go.mod h1:lZjR32SiNo3dP70inVFxaLMyZjmKX1ucS+5O31dbPNM= -github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2 h1:VW2J36F8g/kJn4IkY0JiRFmb1gFcdjiOyltfJLJ0mYU= -github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2/go.mod h1:cOL5YgHUmDG5SAXrsZxFjUECRQQuAqOoqvXhZG5sEUw= github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go v0.16.1-0.20241229121208-76ac3fea5e67 h1:Ii9TmXPBC1GYxRirReSygRZvEGXfAsQRaIipMEzGik0= github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go v0.16.1-0.20241229121208-76ac3fea5e67/go.mod h1:93SROfr+QjgaJ5/jIWtIpLkhaD8Pv8WbdfwvwMNG2p4= github.com/threefoldtech/zbus v1.0.1 h1:3KaEpyOiDYAw+lrAyoQUGIvY9BcjVRXlQ1beBRqhRNk= diff --git a/nodeclient/README.md b/nodeclient/README.md new file mode 100644 index 00000000..dc6fe418 --- /dev/null +++ b/nodeclient/README.md @@ -0,0 +1,53 @@ +# NodeClient + +NodeClient provides a simple interface to interact with ThreeFold nodes through JSON-RPC calls. It supports various operations including system information, network configuration, deployment management, and performance monitoring. + +## Usage + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/threefoldtech/tfgrid-sdk-go/messenger" + "github.com/threefoldtech/zosbase/nodeclient" +) + +func main() { + // Create messenger instance + msgr := messenger.NewMessenger(/* messenger config */) + + // Create node client + client := nodeclient.NewNodeClient(msgr, "node-destination-id") + + ctx := context.Background() + + // Get node version + version, err := client.GetNodeVersion(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Node version: %+v\n", version) + + // Get system diagnostics + diag, err := client.GetSystemDiagnostics(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Diagnostics: %+v\n", diag) + + // List deployments + deployments, err := client.DeploymentList(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Found %d deployments\n", len(deployments)) +} +``` + +## Generate client + +a node client can be generated in any language with proper tool or LLMs from the [openrpc.json](../openrpc.json) file diff --git a/nodeclient/nodeclient.go b/nodeclient/nodeclient.go new file mode 100644 index 00000000..43efb071 --- /dev/null +++ b/nodeclient/nodeclient.go @@ -0,0 +1,251 @@ +package nodeclient + +import ( + "context" + + "github.com/threefoldtech/tfgrid-sdk-go/messenger" + "github.com/threefoldtech/zosbase/pkg" + "github.com/threefoldtech/zosbase/pkg/api" + "github.com/threefoldtech/zosbase/pkg/capacity/dmi" + "github.com/threefoldtech/zosbase/pkg/diagnostics" + "github.com/threefoldtech/zosbase/pkg/geoip" + "github.com/threefoldtech/zosbase/pkg/gridtypes" +) + +type NodeClient struct { + rpcClient *messenger.JSONRPCClient + destination string +} + +func NewNodeClient(msgr *messenger.Messenger, destination string) *NodeClient { + return &NodeClient{ + rpcClient: messenger.NewJSONRPCClient(msgr), + destination: destination, + } +} + +// System Methods + +func (c *NodeClient) GetNodeVersion(ctx context.Context) (api.Version, error) { + var version api.Version + if err := c.rpcClient.Call(ctx, c.destination, "system.version", nil, &version); err != nil { + return api.Version{}, err + } + return version, nil +} + +func (c *NodeClient) GetSystemDMI(ctx context.Context) (dmi.DMI, error) { + var dmiInfo dmi.DMI + if err := c.rpcClient.Call(ctx, c.destination, "system.dmi", nil, &dmiInfo); err != nil { + return dmi.DMI{}, err + } + return dmiInfo, nil +} + +func (c *NodeClient) GetSystemHypervisor(ctx context.Context) (string, error) { + var hypervisor string + if err := c.rpcClient.Call(ctx, c.destination, "system.hypervisor", nil, &hypervisor); err != nil { + return "", err + } + return hypervisor, nil +} + +func (c *NodeClient) GetSystemDiagnostics(ctx context.Context) (diagnostics.Diagnostics, error) { + var diag diagnostics.Diagnostics + if err := c.rpcClient.Call(ctx, c.destination, "system.diagnostics", nil, &diag); err != nil { + return diagnostics.Diagnostics{}, err + } + return diag, nil +} + +func (c *NodeClient) GetSystemFeatures(ctx context.Context) ([]pkg.NodeFeature, error) { + var features []pkg.NodeFeature + if err := c.rpcClient.Call(ctx, c.destination, "system.features", nil, &features); err != nil { + return nil, err + } + return features, nil +} + +// Monitor/Performance Methods + +func (c *NodeClient) GetMonitorSpeed(ctx context.Context) (pkg.IperfTaskResult, error) { + var result pkg.IperfTaskResult + if err := c.rpcClient.Call(ctx, c.destination, "monitor.speed", nil, &result); err != nil { + return pkg.IperfTaskResult{}, err + } + return result, nil +} + +func (c *NodeClient) GetMonitorHealth(ctx context.Context) (pkg.HealthTaskResult, error) { + var result pkg.HealthTaskResult + if err := c.rpcClient.Call(ctx, c.destination, "monitor.health", nil, &result); err != nil { + return pkg.HealthTaskResult{}, err + } + return result, nil +} + +func (c *NodeClient) GetMonitorPublicIp(ctx context.Context) (pkg.PublicIpTaskResult, error) { + var result pkg.PublicIpTaskResult + if err := c.rpcClient.Call(ctx, c.destination, "monitor.publicip", nil, &result); err != nil { + return pkg.PublicIpTaskResult{}, err + } + return result, nil +} + +func (c *NodeClient) GetMonitorBenchmark(ctx context.Context) (pkg.CpuBenchTaskResult, error) { + var result pkg.CpuBenchTaskResult + if err := c.rpcClient.Call(ctx, c.destination, "monitor.benchmark", nil, &result); err != nil { + return pkg.CpuBenchTaskResult{}, err + } + return result, nil +} + +func (c *NodeClient) GetMonitorAll(ctx context.Context) (pkg.AllTaskResult, error) { + var result pkg.AllTaskResult + if err := c.rpcClient.Call(ctx, c.destination, "monitor.all", nil, &result); err != nil { + return pkg.AllTaskResult{}, err + } + return result, nil +} + +// Network Methods + +func (c *NodeClient) GetNetworkWGPorts(ctx context.Context) ([]uint, error) { + var ports []uint + if err := c.rpcClient.Call(ctx, c.destination, "network.wg_ports", nil, &ports); err != nil { + return nil, err + } + return ports, nil +} + +func (c *NodeClient) GetNetworkPublicConfig(ctx context.Context) (pkg.PublicConfig, error) { + var config pkg.PublicConfig + if err := c.rpcClient.Call(ctx, c.destination, "network.public_config", nil, &config); err != nil { + return pkg.PublicConfig{}, err + } + return config, nil +} + +func (c *NodeClient) GetNetworkHasIPv6(ctx context.Context) (bool, error) { + var hasIPv6 bool + if err := c.rpcClient.Call(ctx, c.destination, "network.has_ipv6", nil, &hasIPv6); err != nil { + return false, err + } + return hasIPv6, nil +} + +func (c *NodeClient) GetNetworkPublicIPs(ctx context.Context) ([]string, error) { + var ips []string + if err := c.rpcClient.Call(ctx, c.destination, "network.public_ips", nil, &ips); err != nil { + return nil, err + } + return ips, nil +} + +func (c *NodeClient) GetNetworkPrivateIPs(ctx context.Context, networkName string) ([]string, error) { + params := map[string]any{ + "network_name": networkName, + } + var ips []string + if err := c.rpcClient.Call(ctx, c.destination, "network.private_ips", params, &ips); err != nil { + return nil, err + } + return ips, nil +} + +func (c *NodeClient) GetNetworkInterfaces(ctx context.Context) ([]pkg.Interface, error) { + var interfaces []pkg.Interface + if err := c.rpcClient.Call(ctx, c.destination, "network.interfaces", nil, &interfaces); err != nil { + return nil, err + } + return interfaces, nil +} + +func (c *NodeClient) SetNetworkPublicNIC(ctx context.Context, device string) error { + params := map[string]any{ + "device": device, + } + return c.rpcClient.Call(ctx, c.destination, "network.set_public_nic", params, nil) +} + +func (c *NodeClient) GetNetworkPublicNIC(ctx context.Context) (pkg.ExitDevice, error) { + var device pkg.ExitDevice + if err := c.rpcClient.Call(ctx, c.destination, "network.get_public_nic", nil, &device); err != nil { + return pkg.ExitDevice{}, err + } + return device, nil +} + +// Deployment Methods + +func (c *NodeClient) DeploymentDeploy(ctx context.Context, deployment gridtypes.Deployment) error { + return c.rpcClient.Call(ctx, c.destination, "deployment.deploy", deployment, nil) +} + +func (c *NodeClient) DeploymentUpdate(ctx context.Context, deployment gridtypes.Deployment) error { + return c.rpcClient.Call(ctx, c.destination, "deployment.update", deployment, nil) +} + +func (c *NodeClient) DeploymentGet(ctx context.Context, contractID uint64) (gridtypes.Deployment, error) { + params := map[string]any{ + "contract_id": contractID, + } + var deployment gridtypes.Deployment + if err := c.rpcClient.Call(ctx, c.destination, "deployment.get", params, &deployment); err != nil { + return gridtypes.Deployment{}, err + } + return deployment, nil +} + +func (c *NodeClient) DeploymentList(ctx context.Context) ([]gridtypes.Deployment, error) { + var deployments []gridtypes.Deployment + if err := c.rpcClient.Call(ctx, c.destination, "deployment.list", nil, &deployments); err != nil { + return nil, err + } + return deployments, nil +} + +func (c *NodeClient) DeploymentChanges(ctx context.Context, contractID uint64) ([]gridtypes.Workload, error) { + params := map[string]any{ + "contract_id": contractID, + } + var changes []gridtypes.Workload + if err := c.rpcClient.Call(ctx, c.destination, "deployment.changes", params, &changes); err != nil { + return nil, err + } + return changes, nil +} + +// Other Methods + +func (c *NodeClient) GetGpuList(ctx context.Context) ([]pkg.GPUInfo, error) { + var gpus []pkg.GPUInfo + if err := c.rpcClient.Call(ctx, c.destination, "gpu.list", nil, &gpus); err != nil { + return nil, err + } + return gpus, nil +} + +func (c *NodeClient) GetStoragePools(ctx context.Context) ([]pkg.PoolMetrics, error) { + var pools []pkg.PoolMetrics + if err := c.rpcClient.Call(ctx, c.destination, "storage.pools", nil, &pools); err != nil { + return nil, err + } + return pools, nil +} + +func (c *NodeClient) GetStatistics(ctx context.Context) (pkg.Counters, error) { + var stats pkg.Counters + if err := c.rpcClient.Call(ctx, c.destination, "statistics", nil, &stats); err != nil { + return pkg.Counters{}, err + } + return stats, nil +} + +func (c *NodeClient) GetLocation(ctx context.Context) (geoip.Location, error) { + var location geoip.Location + if err := c.rpcClient.Call(ctx, c.destination, "location.get", nil, &location); err != nil { + return geoip.Location{}, err + } + return location, nil +} diff --git a/openrpc.json b/openrpc.json new file mode 100644 index 00000000..1495932e --- /dev/null +++ b/openrpc.json @@ -0,0 +1,2035 @@ +{ + "openrpc": "1.2.3", + "info": { + "title": "ZOS Mycelium API", + "description": "This is an API for interacting with the ZOS nodes over Mycelium.", + "version": "1.0.0" + }, + "methods": [ + { + "name": "system.version", + "description": "Retrieves the current version information for ZOS (Zero OS) and Zinit process manager. This method executes 'zinit -V' command to get Zinit version and queries the version monitor stub for ZOS version.", + "params": [], + "result": { + "name": "Version", + "description": "Version information containing ZOS and Zinit versions", + "schema": { + "$ref": "#/components/schemas/Version" + } + }, + "examples": [ + { + "name": "Get system version", + "params": [], + "result": { + "name": "Version", + "value": { + "zos": "0.5.6", + "zinit": "0.2.14" + } + } + } + ] + }, + { + "name": "system.hypervisor", + "description": "Detects the hypervisor type running on the node by querying the resource oracle. Returns the hypervisor name if the node is running in a virtualized environment, or indicates bare metal if no hypervisor is detected.", + "params": [], + "result": { + "name": "Hypervisor", + "description": "The name of the detected hypervisor (e.g., 'kvm', 'vmware', 'xen') or 'bare-metal' if no hypervisor is detected", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Get hypervisor type", + "params": [], + "result": { + "name": "Hypervisor", + "value": "kvm" + } + } + ] + }, + { + "name": "system.diagnostics", + "description": "Performs comprehensive system diagnostics including CPU usage, memory statistics, disk information, and running processes. Also checks the health status of ZOS modules and overall system health.", + "params": [], + "result": { + "name": "Diagnostics", + "description": "Comprehensive system diagnostic information including CPU, memory, disk, and process statistics", + "schema": { + "$ref": "#/components/schemas/Diagnostics" + } + }, + "examples": [ + { + "name": "Get system diagnostics", + "params": [], + "result": { + "name": "Diagnostics", + "value": { + "cpu": [{"user": 15.2, "system": 8.1, "idle": 76.7}], + "memory": {"used": 2147483648, "total": 8589934592, "cached": 1073741824, "buffers": 536870912}, + "disks": [{"device": "/dev/sda1", "size": 107374182400, "used": 53687091200, "filesystem": "ext4", "mounted": "/"}], + "processes": [{"command": "systemd", "cpu": 0.1, "memory": 0.5, "rss": 8388608, "vms": 167772160}] + } + } + } + ] + }, + { + "name": "system.dmi", + "description": "Retrieves Desktop Management Interface (DMI) information from the system BIOS/UEFI. This includes detailed hardware information such as system manufacturer, product name, BIOS version, memory modules, and other hardware components.", + "params": [], + "result": { + "name": "DMI", + "description": "DMI hardware information including system details, BIOS information, and hardware components", + "schema": { + "$ref": "#/components/schemas/DMI" + } + }, + "examples": [ + { + "name": "Get DMI information", + "params": [], + "result": { + "name": "DMI", + "value": { + "tooling": {"aggregator": "dmidecode", "decoder": "dmi"}, + "sections": [ + { + "handleline": "Handle 0x0001, DMI type 1, 27 bytes", + "typestr": "System Information", + "typenum": 1, + "subsections": [ + { + "title": "System Information", + "properties": [ + {"name": "Manufacturer", "value": "Dell Inc."}, + {"name": "Product Name", "value": "PowerEdge R740"} + ] + } + ] + } + ] + } + } + } + ] + }, + { + "name": "system.features", + "description": "Returns a list of supported node features based on the current system configuration and available services. Features indicate what types of workloads and capabilities the node supports.", + "params": [], + "result": { + "name": "NodeFeatures", + "description": "Array of supported node features as strings", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": [ + { + "name": "Get node features", + "params": [], + "result": { + "name": "NodeFeatures", + "value": ["zmachine", "zdb", "network", "volume", "kubernetes"] + } + } + ] + }, + { + "name": "monitor.speed", + "description": "Performs network speed tests using iperf to measure upload and download speeds to other nodes in the grid. Tests network connectivity and bandwidth performance between nodes.", + "params": [], + "result": { + "name": "SpeedResult", + "description": "Network speed test results including upload/download speeds and CPU utilization during tests", + "schema": { + "$ref": "#/components/schemas/IperfTaskResult" + } + }, + "examples": [ + { + "name": "Get network speed test results", + "params": [], + "result": { + "name": "SpeedResult", + "value": { + "name": "iperf", + "description": "Network speed test using iperf", + "timestamp": 1640995200, + "result": [ + { + "node_id": 123, + "node_ip": "192.168.1.100", + "test_type": "upload", + "upload_speed": 95.5, + "download_speed": 87.2, + "cpu_report": {"user": 12.5, "system": 8.3, "idle": 79.2} + } + ] + } + } + } + ] + }, + { + "name": "monitor.health", + "description": "Performs comprehensive health checks on the node including system services, network connectivity, and resource availability. Returns detailed health status and any detected issues.", + "params": [], + "result": { + "name": "HealthResult", + "description": "Health check results including test status and any errors detected", + "schema": { + "$ref": "#/components/schemas/HealthTaskResult" + } + }, + "examples": [ + { + "name": "Get health check results", + "params": [], + "result": { + "name": "HealthResult", + "value": { + "name": "health_check", + "description": "System health check", + "timestamp": 1640995200, + "result": { + "test_name": "system_health", + "errors": [] + } + } + } + } + ] + }, + { + "name": "monitor.publicip", + "description": "Tests public IP connectivity and accessibility from external networks. Verifies that public IPs assigned to the node are reachable and properly configured.", + "params": [], + "result": { + "name": "PublicIpResult", + "description": "Public IP connectivity test results showing the status of each public IP", + "schema": { + "$ref": "#/components/schemas/PublicIpTaskResult" + } + }, + "examples": [ + { + "name": "Get public IP test results", + "params": [], + "result": { + "name": "PublicIpResult", + "value": { + "name": "public_ip_test", + "description": "Public IP connectivity test", + "timestamp": 1640995200, + "result": [ + { + "ip": "203.0.113.10", + "state": "ok", + "reason": "IP is reachable" + } + ] + } + } + } + ] + }, + { + "name": "monitor.benchmark", + "description": "Performs CPU benchmark tests to measure single-core and multi-core performance. Uses standardized benchmarking tools to assess computational capabilities.", + "params": [], + "result": { + "name": "BenchmarkResult", + "description": "CPU benchmark results including single-core and multi-core performance scores", + "schema": { + "$ref": "#/components/schemas/CpuBenchTaskResult" + } + }, + "examples": [ + { + "name": "Get CPU benchmark results", + "params": [], + "result": { + "name": "BenchmarkResult", + "value": { + "name": "cpu_benchmark", + "description": "CPU performance benchmark", + "timestamp": 1640995200, + "result": { + "single": 1250.5, + "multi": 8500.2, + "threads": 8, + "workloads": 4 + } + } + } + } + ] + }, + { + "name": "monitor.all", + "description": "Executes all monitoring tests (speed, health, public IP, and benchmark) and returns combined results. Provides a comprehensive overview of node performance and status.", + "params": [], + "result": { + "name": "AllTaskResults", + "description": "Combined results from all monitoring tests including speed, health, public IP, and benchmark tests", + "schema": { + "$ref": "#/components/schemas/AllTaskResult" + } + }, + "examples": [ + { + "name": "Get all monitoring test results", + "params": [], + "result": { + "name": "AllTaskResults", + "value": { + "health_check": { + "name": "health_check", + "description": "System health check", + "timestamp": 1640995200, + "result": {"test_name": "system_health", "errors": []} + }, + "iperf": { + "name": "iperf", + "description": "Network speed test", + "timestamp": 1640995200, + "result": [{"node_id": 123, "upload_speed": 95.5, "download_speed": 87.2}] + }, + "public_ip": { + "name": "public_ip_test", + "description": "Public IP test", + "timestamp": 1640995200, + "result": [{"ip": "203.0.113.10", "state": "ok"}] + }, + "cpu_benchmark": { + "name": "cpu_benchmark", + "description": "CPU benchmark", + "timestamp": 1640995200, + "result": {"single": 1250.5, "multi": 8500.2} + } + } + } + } + ] + }, + { + "name": "network.wg_ports", + "description": "Retrieves the list of WireGuard ports currently in use by the node. These ports are used for secure VPN connections between nodes in the grid. Not supported in light mode.", + "params": [], + "result": { + "name": "WGPorts", + "description": "List of WireGuard ports currently allocated and in use", + "schema": { + "$ref": "#/components/schemas/WGPorts" + } + }, + "examples": [ + { + "name": "Get WireGuard ports", + "params": [], + "result": { + "name": "WGPorts", + "value": { + "ports": [51820, 51821, 51822] + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "method is not supported in light mode" + } + ] + }, + { + "name": "network.public_config", + "description": "Retrieves the public network configuration for the node including IPv4/IPv6 addresses, gateways, and domain information. Not supported in light mode.", + "params": [], + "result": { + "name": "PublicConfig", + "description": "Public network configuration including IP addresses, gateways, and domain settings", + "schema": { + "$ref": "#/components/schemas/PublicConfig" + } + }, + "examples": [ + { + "name": "Get public network configuration", + "params": [], + "result": { + "name": "PublicConfig", + "value": { + "type": "static", + "ipv4": "203.0.113.10/24", + "ipv6": "2001:db8::10/64", + "gw4": "203.0.113.1", + "gw6": "2001:db8::1", + "domain": "example.com" + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "method is not supported in light mode" + } + ] + }, + { + "name": "network.has_ipv6", + "description": "Checks whether the node has IPv6 connectivity available. Returns false in light mode or if no IPv6 subnet is configured.", + "params": [], + "result": { + "name": "HasIpv6", + "description": "Boolean indicating whether IPv6 connectivity is available on the node", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Check IPv6 availability", + "params": [], + "result": { + "name": "HasIpv6", + "value": true + } + } + ] + }, + { + "name": "network.public_ips", + "description": "Lists all public IP addresses currently allocated and in use by deployments on the node. Not supported in light mode.", + "params": [], + "result": { + "name": "PublicIps", + "description": "List of public IP addresses allocated to the node", + "schema": { + "$ref": "#/components/schemas/Ips" + } + }, + "examples": [ + { + "name": "Get public IP addresses", + "params": [], + "result": { + "name": "PublicIps", + "value": { + "ips": ["203.0.113.10", "203.0.113.11", "2001:db8::10"] + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "method is not supported in light mode" + } + ] + }, + { + "name": "network.private_ips", + "description": "Lists private IP addresses allocated to a specific network for the requesting twin. Requires authentication and returns IPs only for networks owned by the caller's twin ID.", + "params": [ + { + "name": "network_name", + "description": "Name of the private network to query for IP addresses", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "PrivateIps", + "description": "List of private IP addresses allocated to the specified network", + "schema": { + "$ref": "#/components/schemas/Ips" + } + }, + "examples": [ + { + "name": "Get private IPs for network", + "params": [ + { + "name": "network_name", + "value": "mynetwork" + } + ], + "result": { + "name": "PrivateIps", + "value": { + "ips": ["10.1.0.2", "10.1.0.3", "10.1.0.4"] + } + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "network.interfaces", + "description": "Lists network interfaces available on the node including their IP addresses and MAC addresses. In light mode, returns interfaces from the light networker. In full mode, returns specific interfaces (zos, ygg) with their configurations.", + "params": [], + "result": { + "name": "Interfaces", + "description": "Array of network interfaces with their IP addresses and MAC addresses", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Interface" + } + } + }, + "examples": [ + { + "name": "Get network interfaces", + "params": [], + "result": { + "name": "Interfaces", + "value": [ + { + "name": "zos", + "ips": ["192.168.1.100", "fe80::1"], + "mac": "00:11:22:33:44:55" + }, + { + "name": "ygg", + "ips": ["200:1234::1"], + "mac": "00:11:22:33:44:56" + } + ] + } + } + ] + }, + { + "name": "network.set_public_nic", + "description": "Sets the public network interface device for the node. This configures which physical network interface should be used for public traffic. Not supported in light mode.", + "params": [ + { + "name": "device", + "description": "Name of the network device to set as the public interface (e.g., 'eth0', 'ens3')", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "SetResult", + "description": "Empty object indicating successful configuration", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Set public network interface", + "params": [ + { + "name": "device", + "value": "eth0" + } + ], + "result": { + "name": "SetResult", + "value": {} + } + } + ], + "errors": [ + { + "code": -32000, + "message": "method is not supported in light mode" + } + ] + }, + { + "name": "network.get_public_nic", + "description": "Retrieves the current public network interface configuration including whether it's in single or dual mode and the associated physical interface. Not supported in light mode.", + "params": [], + "result": { + "name": "PublicNIC", + "description": "Public network interface configuration details", + "schema": { + "$ref": "#/components/schemas/ExitDevice" + } + }, + "examples": [ + { + "name": "Get public network interface", + "params": [], + "result": { + "name": "PublicNIC", + "value": { + "is_single": false, + "is_dual": true, + "dual_interface": "eth0" + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "method is not supported in light mode" + } + ] + }, + { + "name": "network.admin.interfaces", + "description": "Lists all network interfaces available on the node for administrative purposes. This provides a comprehensive view of all network interfaces regardless of their current configuration or usage.", + "params": [], + "result": { + "name": "Interfaces", + "description": "Array of all network interfaces available for administrative configuration", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Interface" + } + } + }, + "examples": [ + { + "name": "Get all network interfaces", + "params": [], + "result": { + "name": "Interfaces", + "value": [ + { + "name": "eth0", + "ips": ["192.168.1.100"], + "mac": "00:11:22:33:44:55" + }, + { + "name": "eth1", + "ips": [], + "mac": "00:11:22:33:44:56" + }, + { + "name": "lo", + "ips": ["127.0.0.1", "::1"], + "mac": "00:00:00:00:00:00" + } + ] + } + } + ] + }, + { + "name": "deployment.deploy", + "description": "Creates a new deployment on the node with the specified workloads. The deployment must have a valid signature and the contract must exist on the blockchain with the correct contract hash. Requires authentication via twin ID.", + "params": [ + { + "name": "deployment", + "description": "Complete deployment specification including workloads, metadata, and signature requirements", + "required": true, + "schema": { + "$ref": "#/components/schemas/Deployment" + } + } + ], + "result": { + "name": "createResult", + "description": "Empty object indicating successful deployment creation", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Deploy workloads", + "params": [ + { + "name": "deployment", + "value": { + "version": 1, + "twin_id": 123, + "contract_id": 456, + "metadata": "deployment metadata", + "description": "Test deployment", + "expiration": 1672531200, + "workloads": [ + { + "version": 1, + "name": "test-vm", + "type": "zmachine", + "data": {}, + "metadata": "vm metadata", + "description": "Test virtual machine" + } + ] + } + } + ], + "result": { + "name": "createResult", + "value": {} + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "deployment.update", + "description": "Updates an existing deployment with new workload configurations. The deployment must already exist and the caller must be the owner (same twin ID). Requires valid signature for the updated deployment.", + "params": [ + { + "name": "deployment", + "description": "Updated deployment specification with modified workloads or metadata", + "required": true, + "schema": { + "$ref": "#/components/schemas/Deployment" + } + } + ], + "result": { + "name": "updateResult", + "description": "Empty object indicating successful deployment update", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Update deployment", + "params": [ + { + "name": "deployment", + "value": { + "version": 2, + "twin_id": 123, + "contract_id": 456, + "metadata": "updated metadata", + "description": "Updated deployment", + "expiration": 1672531200, + "workloads": [ + { + "version": 2, + "name": "test-vm", + "type": "zmachine", + "data": {}, + "metadata": "updated vm metadata", + "description": "Updated virtual machine" + } + ] + } + } + ], + "result": { + "name": "updateResult", + "value": {} + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "deployment.get", + "description": "Retrieves a specific deployment by its contract ID. Only returns deployments owned by the requesting twin ID for security purposes.", + "params": [ + { + "name": "contract_id", + "description": "Unique contract ID of the deployment to retrieve", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "result": { + "name": "Deployment", + "description": "Complete deployment specification including all workloads and metadata", + "schema": { + "$ref": "#/components/schemas/Deployment" + } + }, + "examples": [ + { + "name": "Get deployment by contract ID", + "params": [ + { + "name": "contract_id", + "value": 456 + } + ], + "result": { + "name": "Deployment", + "value": { + "version": 1, + "twin_id": 123, + "contract_id": 456, + "metadata": "deployment metadata", + "description": "Test deployment", + "expiration": 1672531200, + "workloads": [ + { + "version": 1, + "name": "test-vm", + "type": "zmachine", + "data": {}, + "metadata": "vm metadata", + "description": "Test virtual machine" + } + ] + } + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "deployment.list", + "description": "Lists all deployments owned by the requesting twin ID. Returns an array of deployments with their complete specifications and current status.", + "params": [], + "result": { + "name": "Deployments", + "description": "Array of all deployments owned by the requesting twin", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Deployment" + } + } + }, + "examples": [ + { + "name": "List all deployments", + "params": [], + "result": { + "name": "Deployments", + "value": [ + { + "version": 1, + "twin_id": 123, + "contract_id": 456, + "metadata": "deployment metadata", + "description": "Test deployment", + "expiration": 1672531200, + "workloads": [ + { + "version": 1, + "name": "test-vm", + "type": "zmachine", + "data": {}, + "metadata": "vm metadata", + "description": "Test virtual machine" + } + ] + } + ] + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "deployment.changes", + "description": "Retrieves workloads that have changed or been updated in a specific deployment. This is useful for tracking deployment modifications and their current state.", + "params": [ + { + "name": "contract_id", + "description": "Contract ID of the deployment to check for changes", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "result": { + "name": "Workloads", + "description": "Array of workloads that have been modified in the deployment", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Workload" + } + } + }, + "examples": [ + { + "name": "Get deployment changes", + "params": [ + { + "name": "contract_id", + "value": 456 + } + ], + "result": { + "name": "Workloads", + "value": [ + { + "version": 2, + "name": "test-vm", + "type": "zmachine", + "data": {}, + "metadata": "updated vm metadata", + "description": "Updated virtual machine", + "result": { + "created": 1640995200, + "state": "ok", + "message": "Workload deployed successfully", + "data": {} + } + } + ] + } + } + ], + "errors": [ + { + "code": -32001, + "message": "could not get twin_id from context" + } + ] + }, + { + "name": "deployment.delete", + "description": "Deletion over the API is disabled for security reasons. To delete a deployment, you must cancel the associated contract on the blockchain instead.", + "params": [ + { + "name": "contract_id", + "description": "Contract ID of the deployment to delete (operation will fail)", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "result": { + "name": "deleteResult", + "description": "This operation always fails - deletion must be done via contract cancellation", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Attempt to delete deployment", + "params": [ + { + "name": "contract_id", + "value": 456 + } + ], + "result": { + "name": "deleteResult", + "value": {} + } + } + ], + "errors": [ + { + "code": -32002, + "message": "deletion over the api is disabled, please cancel your contract instead" + } + ] + }, + { + "name": "gpu.list", + "description": "Lists all GPU devices available on the node including their vendor, device information, and current contract assignment. Only discrete GPUs from whitelisted vendors (NVIDIA, AMD) are included.", + "params": [], + "result": { + "name": "Gpus", + "description": "Array of GPU devices with vendor, device, and contract information", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GPU" + } + } + }, + "examples": [ + { + "name": "List available GPUs", + "params": [], + "result": { + "name": "Gpus", + "value": [ + { + "id": "0000:01:00.0", + "vendor": "NVIDIA Corporation", + "device": "GeForce RTX 3080", + "contract": 0 + }, + { + "id": "0000:02:00.0", + "vendor": "Advanced Micro Devices, Inc.", + "device": "Radeon RX 6800 XT", + "contract": 789 + } + ] + } + } + ] + }, + { + "name": "storage.pools", + "description": "Retrieves metrics for all storage pools on the node including pool names, types (SSD/HDD), total size, and used space. Provides insight into storage capacity and utilization.", + "params": [], + "result": { + "name": "PoolMetrics", + "description": "Array of storage pool metrics with size and usage information", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PoolMetrics" + } + } + }, + "examples": [ + { + "name": "Get storage pool metrics", + "params": [], + "result": { + "name": "PoolMetrics", + "value": [ + { + "name": "ssd_pool", + "type": "SSD", + "size": 1099511627776, + "used": 549755813888 + }, + { + "name": "hdd_pool", + "type": "HDD", + "size": 4398046511104, + "used": 1099511627776 + } + ] + } + } + ] + }, + { + "name": "statistics", + "description": "Retrieves comprehensive node resource statistics including total capacity, used resources, system reserved resources, and user deployment statistics. Provides overview of node utilization and activity.", + "params": [], + "result": { + "name": "Counters", + "description": "Node resource statistics and usage counters", + "schema": { + "$ref": "#/components/schemas/Counters" + } + }, + "examples": [ + { + "name": "Get node statistics", + "params": [], + "result": { + "name": "Counters", + "value": { + "users": { + "deployments": 15, + "workloads": 42, + "last_deployment_timestamp": 1640995200 + } + } + } + } + ] + }, + { + "name": "vm.logs", + "description": "Retrieves log content for a specific virtual machine by reading the log file from the VM logs directory. Useful for debugging and monitoring VM operations.", + "params": [ + { + "name": "file_name", + "description": "Name of the log file to retrieve (relative to VM logs directory)", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "LogContent", + "description": "Raw log file content as a string", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Get VM logs", + "params": [ + { + "name": "file_name", + "value": "vm-123.log" + } + ], + "result": { + "name": "LogContent", + "value": "2024-01-01 12:00:00 [INFO] VM started successfully\n2024-01-01 12:00:01 [INFO] Network interface configured\n2024-01-01 12:00:02 [INFO] VM ready for connections" + } + } + ], + "errors": [ + { + "code": -32003, + "message": "failed to read file, path: /var/cache/modules/vmd/logs/vm-123.log, no such file or directory" + } + ] + }, + { + "name": "location.get", + "description": "Retrieves the geographical location of the node using GeoIP services. Location data is cached for 24 hours to reduce external API calls. Includes coordinates, country, city, and continent information.", + "params": [], + "result": { + "name": "Location", + "description": "Geographical location information for the node", + "schema": { + "$ref": "#/components/schemas/Location" + } + }, + "examples": [ + { + "name": "Get node location", + "params": [], + "result": { + "name": "Location", + "value": { + "country": "United States", + "city": "New York", + "longitude": -74.0059, + "latitude": 40.7128, + "continent": "North America", + "region_name": "New York", + "region_code": "NY" + } + } + } + ], + "errors": [ + { + "code": -32004, + "message": "failed to fetch location information" + } + ] + } + ], + "components": { + "schemas": { + "Version": { + "type": "object", + "description": "System version information containing ZOS and Zinit versions", + "properties": { + "zos": { + "type": "string", + "description": "Version of ZOS (Zero OS) currently running on the node" + }, + "zinit": { + "type": "string", + "description": "Version of Zinit process manager, or error message if version detection failed" + } + }, + "required": ["zos", "zinit"] + }, + "DMI": { + "type": "object", + "description": "Desktop Management Interface information containing hardware details from system BIOS/UEFI", + "properties": { + "tooling": { + "$ref": "#/components/schemas/Tooling", + "description": "Information about the tools used to gather DMI data" + }, + "sections": { + "type": "array", + "description": "Array of DMI sections containing hardware information", + "items": { + "$ref": "#/components/schemas/Section" + } + } + }, + "required": ["tooling", "sections"] + }, + "Tooling": { + "type": "object", + "description": "Information about the tools used to gather and decode DMI data", + "properties": { + "aggregator": { + "type": "string", + "description": "Tool used to aggregate DMI data (typically 'dmidecode')" + }, + "decoder": { + "type": "string", + "description": "Tool used to decode DMI data (typically 'dmi')" + } + }, + "required": ["aggregator", "decoder"] + }, + "Section": { + "type": "object", + "description": "DMI section containing hardware information for a specific component type", + "properties": { + "handleline": { + "type": "string", + "description": "DMI handle line identifier (e.g., 'Handle 0x0001, DMI type 1, 27 bytes')" + }, + "typestr": { + "type": "string", + "description": "Human-readable description of the DMI type (e.g., 'System Information', 'BIOS Information')" + }, + "typenum": { + "type": "integer", + "description": "Numeric DMI type identifier" + }, + "subsections": { + "type": "array", + "description": "Array of subsections containing detailed hardware properties", + "items": { + "$ref": "#/components/schemas/SubSection" + } + } + }, + "required": ["handleline", "typestr", "typenum", "subsections"] + }, + "SubSection": { + "type": "object", + "description": "DMI subsection containing grouped hardware properties", + "properties": { + "title": { + "type": "string", + "description": "Title of the subsection (e.g., 'System Information', 'Memory Device')" + }, + "properties": { + "type": "array", + "description": "Array of hardware properties within this subsection", + "items": { + "$ref": "#/components/schemas/PropertyData" + } + } + }, + "required": ["title", "properties"] + }, + "PropertyData": { + "type": "object", + "description": "Individual hardware property with name, value, and optional list items", + "properties": { + "name": { + "type": "string", + "description": "Name of the hardware property (e.g., 'Manufacturer', 'Product Name', 'Serial Number')" + }, + "value": { + "type": "string", + "description": "Value of the hardware property" + }, + "items": { + "type": "array", + "description": "Optional array of related items for properties that have multiple values", + "items": { + "type": "string" + } + } + }, + "required": ["name"] + }, + "Diagnostics": { + "type": "object", + "description": "Comprehensive system diagnostic information including CPU, memory, disk, and process statistics", + "properties": { + "cpu": { + "type": "array", + "description": "Array of CPU usage statistics, typically one entry per CPU core", + "items": { + "$ref": "#/components/schemas/CPUInfo" + } + }, + "memory": { + "description": "Memory usage statistics including RAM and swap information", + "$ref": "#/components/schemas/MemoryInfo" + }, + "disks": { + "type": "array", + "description": "Array of disk usage information for all mounted filesystems", + "items": { + "$ref": "#/components/schemas/DiskInfo" + } + }, + "processes": { + "type": "array", + "description": "Array of running process information with resource usage", + "items": { + "$ref": "#/components/schemas/ProcessesInfo" + } + } + }, + "required": ["cpu", "memory", "disks", "processes"] + }, + "CPUInfo": { + "type": "object", + "description": "CPU usage statistics showing time spent in different modes", + "properties": { + "user": { + "type": "number", + "description": "Percentage of time spent in user mode" + }, + "system": { + "type": "number", + "description": "Percentage of time spent in system/kernel mode" + }, + "idle": { + "type": "number", + "description": "Percentage of time CPU was idle" + } + }, + "required": ["user", "system", "idle"] + }, + "MemoryInfo": { + "type": "object", + "description": "Memory usage statistics including RAM and swap information", + "properties": { + "used": { + "type": "integer", + "description": "Used memory in bytes" + }, + "total": { + "type": "integer", + "description": "Total available memory in bytes" + }, + "cached": { + "type": "integer", + "description": "Cached memory in bytes" + }, + "buffers": { + "type": "integer", + "description": "Buffer memory in bytes" + }, + "limit": { + "type": "integer", + "description": "Memory limit in bytes (if applicable)" + }, + "usedswap": { + "type": "integer", + "description": "Used swap space in bytes" + }, + "totalswap": { + "type": "integer", + "description": "Total swap space in bytes" + } + }, + "required": ["used", "total"] + }, + "DiskInfo": { + "type": "object", + "description": "Disk usage information for a mounted filesystem", + "properties": { + "device": { + "type": "string", + "description": "Device path of the disk (e.g., '/dev/sda1', '/dev/nvme0n1p1')" + }, + "size": { + "type": "integer", + "description": "Total size of the filesystem in bytes" + }, + "used": { + "type": "integer", + "description": "Used space on the filesystem in bytes" + }, + "filesystem": { + "type": "string", + "description": "Filesystem type (e.g., 'ext4', 'xfs', 'btrfs')" + }, + "mounted": { + "type": "string", + "description": "Mount point path where the filesystem is mounted" + } + }, + "required": ["device", "size", "used", "filesystem", "mounted"] + }, + "ProcessesInfo": { + "type": "object", + "description": "Process information including resource usage statistics", + "properties": { + "command": { + "type": "string", + "description": "Command name or executable path of the process" + }, + "cpu": { + "type": "number", + "description": "CPU usage percentage for this process" + }, + "memory": { + "type": "number", + "description": "Memory usage percentage for this process" + }, + "rss": { + "type": "integer", + "description": "Resident Set Size - physical memory currently used by the process in bytes" + }, + "vms": { + "type": "integer", + "description": "Virtual Memory Size - total virtual memory used by the process in bytes" + }, + "swap": { + "type": "integer", + "description": "Swap memory used by the process in bytes" + }, + "time": { + "type": "integer", + "description": "Total CPU time used by the process in seconds" + } + }, + "required": ["command"] + }, + + "GPU": { + "type": "object", + "description": "GPU device information including vendor, device details, and contract assignment", + "properties": { + "id": { + "type": "string", + "description": "PCI slot identifier for the GPU device (e.g., '0000:01:00.0')" + }, + "vendor": { + "type": "string", + "description": "GPU vendor name (e.g., 'NVIDIA Corporation', 'Advanced Micro Devices, Inc.')" + }, + "device": { + "type": "string", + "description": "GPU device model name (e.g., 'GeForce RTX 3080', 'Radeon RX 6800 XT')" + }, + "contract": { + "type": "integer", + "description": "Contract ID currently using this GPU, or 0 if available" + } + }, + "required": ["id", "vendor", "device", "contract"] + }, + + "PoolMetrics": { + "type": "object", + "description": "Storage pool metrics including capacity and usage information", + "properties": { + "name": { + "type": "string", + "description": "Name of the storage pool" + }, + "type": { + "type": "string", + "description": "Storage type (e.g., 'SSD', 'HDD')" + }, + "size": { + "type": "integer", + "description": "Total pool size in bytes" + }, + "used": { + "type": "integer", + "description": "Used space in the pool in bytes" + } + }, + "required": ["name", "type", "size", "used"] + }, + "Counters": { + "type": "object", + "description": "Node resource statistics and usage counters providing overview of capacity and activity", + "properties": { + "total": { + "description": "Total system capacity available on the node", + "$ref": "#/components/schemas/Capacity" + }, + "used": { + "description": "Currently used capacity including user and system resources", + "$ref": "#/components/schemas/Capacity" + }, + "system": { + "description": "System reserved capacity for ZOS operations", + "$ref": "#/components/schemas/Capacity" + }, + "users": { + "description": "User deployment and workload statistics", + "$ref": "#/components/schemas/UsersCounters" + }, + "open_connections": { + "type": "integer", + "description": "Number of currently open network connections" + } + }, + "required": ["users"] + }, + "UsersCounters": { + "type": "object", + "description": "Statistics about user deployments and workloads on the node", + "properties": { + "deployments": { + "type": "integer", + "description": "Total number of active deployments on the node" + }, + "workloads": { + "type": "integer", + "description": "Total number of active workloads across all deployments" + }, + "last_deployment_timestamp": { + "type": "integer", + "description": "Unix timestamp of the most recent deployment activity" + } + }, + "required": ["deployments", "workloads", "last_deployment_timestamp"] + }, + "Capacity": { + "type": "object", + "description": "Resource capacity information including CPU, memory, and storage", + "properties": { + "cru": { + "type": "integer", + "description": "Compute Resource Units (CPU cores)" + }, + "mru": { + "type": "integer", + "description": "Memory Resource Units in bytes" + }, + "hru": { + "type": "integer", + "description": "HDD Resource Units in bytes" + }, + "sru": { + "type": "integer", + "description": "SSD Resource Units in bytes" + } + } + }, + "CpuBenchTaskResult": { + "type": "object", + "description": "CPU benchmark test result containing performance scores and metadata", + "properties": { + "description": { + "type": "string", + "description": "Description of the CPU benchmark test" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp when the benchmark was executed" + }, + "result": { + "$ref": "#/components/schemas/CPUBenchmarkResult", + "description": "CPU benchmark performance results" + }, + "name": { + "type": "string", + "description": "Name identifier for the benchmark test" + } + }, + "required": ["name", "description", "timestamp", "result"] + }, + "CPUBenchmarkResult": { + "type": "object", + "description": "CPU benchmark performance scores and system information", + "properties": { + "single": { + "type": "number", + "description": "Single-core performance score" + }, + "multi": { + "type": "number", + "description": "Multi-core performance score" + }, + "threads": { + "type": "integer", + "description": "Number of CPU threads available" + }, + "workloads": { + "type": "integer", + "description": "Number of workloads used in the benchmark" + } + }, + "required": ["single", "multi", "threads", "workloads"] + }, + "HealthTaskResult": { + "type": "object", + "description": "Health check test result containing system status and any detected issues", + "properties": { + "name": { + "type": "string", + "description": "Name identifier for the health check test" + }, + "description": { + "type": "string", + "description": "Description of the health check test" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp when the health check was executed" + }, + "result": { + "$ref": "#/components/schemas/HealthReport", + "description": "Health check results and error information" + } + }, + "required": ["name", "description", "timestamp", "result"] + }, + "HealthReport": { + "type": "object", + "description": "Health check report containing test results and any detected errors", + "properties": { + "errors": { + "type": "array", + "description": "Array of error messages detected during health checks (empty if no errors)", + "items": { + "type": "string" + } + }, + "test_name": { + "type": "string", + "description": "Name of the specific health test that was performed" + } + }, + "required": ["errors", "test_name"] + }, + "IperfTaskResult": { + "type": "object", + "description": "Network speed test result using iperf containing bandwidth measurements", + "properties": { + "name": { + "type": "string", + "description": "Name identifier for the iperf test" + }, + "description": { + "type": "string", + "description": "Description of the iperf network speed test" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp when the iperf test was executed" + }, + "result": { + "type": "array", + "description": "Array of iperf test results for different target nodes", + "items": { + "$ref": "#/components/schemas/IperfResult" + } + } + }, + "required": ["name", "description", "timestamp", "result"] + }, + "IperfResult": { + "type": "object", + "description": "Individual iperf test result for a specific target node", + "properties": { + "node_id": { + "type": "integer", + "description": "Target node ID for the iperf test" + }, + "node_ip": { + "type": "string", + "description": "IP address of the target node" + }, + "test_type": { + "type": "string", + "description": "Type of test performed (e.g., 'upload', 'download')" + }, + "error": { + "type": "string", + "description": "Error message if the test failed (omitted if successful)" + }, + "cpu_report": { + "$ref": "#/components/schemas/CPUUtilizationPercent", + "description": "CPU utilization during the iperf test" + }, + "upload_speed": { + "type": "number", + "description": "Upload speed in Mbps (megabits per second)" + }, + "download_speed": { + "type": "number", + "description": "Download speed in Mbps (megabits per second)" + } + }, + "required": ["node_id", "node_ip", "test_type"] + }, + "CPUUtilizationPercent": { + "type": "object", + "description": "CPU utilization percentages during a test or operation", + "properties": { + "user": { + "type": "number", + "description": "Percentage of CPU time spent in user mode" + }, + "system": { + "type": "number", + "description": "Percentage of CPU time spent in system/kernel mode" + }, + "idle": { + "type": "number", + "description": "Percentage of CPU time that was idle" + } + }, + "required": ["user", "system", "idle"] + }, + "PublicIpTaskResult": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "timestamp": { + "type": "integer" + }, + "result": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IPReport" + } + } + } + }, + "IPReport": { + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "state": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "AllTaskResult": { + "type": "object", + "properties": { + "health_check": { + "$ref": "#/components/schemas/HealthTaskResult" + }, + "iperf": { + "$ref": "#/components/schemas/IperfTaskResult" + }, + "public_ip": { + "$ref": "#/components/schemas/PublicIpTaskResult" + }, + "cpu_benchmark": { + "$ref": "#/components/schemas/CpuBenchTaskResult" + } + } + }, + + "Interface": { + "type": "object", + "description": "Network interface information including name, IP addresses, and MAC address", + "properties": { + "name": { + "type": "string", + "description": "Name of the network interface (e.g., 'eth0', 'zos', 'ygg')" + }, + "ips": { + "type": "array", + "description": "List of IP addresses assigned to this interface", + "items": { + "type": "string" + } + }, + "mac": { + "type": "string", + "description": "MAC address of the network interface" + } + }, + "required": ["name", "ips", "mac"] + }, + "Ips": { + "type": "object", + "description": "Container for a list of IP addresses", + "properties": { + "ips": { + "type": "array", + "description": "Array of IP addresses as strings", + "items": { + "type": "string" + } + } + }, + "required": ["ips"] + }, + "WGPorts": { + "type": "object", + "description": "Container for WireGuard port numbers", + "properties": { + "ports": { + "type": "array", + "description": "Array of WireGuard port numbers currently in use", + "items": { + "type": "integer" + } + } + }, + "required": ["ports"] + }, + "PublicConfig": { + "type": "object", + "description": "Public network configuration including IP addresses, gateways, and domain information", + "properties": { + "type": { + "type": "string", + "description": "Type of public network configuration (e.g., 'static', 'dhcp')" + }, + "ipv4": { + "type": "string", + "description": "IPv4 address with CIDR notation (e.g., '203.0.113.10/24')" + }, + "ipv6": { + "type": "string", + "description": "IPv6 address with CIDR notation (e.g., '2001:db8::10/64')" + }, + "gw4": { + "type": "string", + "description": "IPv4 gateway address" + }, + "gw6": { + "type": "string", + "description": "IPv6 gateway address" + }, + "domain": { + "type": "string", + "description": "Domain name associated with the public configuration" + } + } + }, + "ExitDevice": { + "type": "object", + "properties": { + "is_single": { + "type": "boolean", + "description": "Set to true if br-pub is connected to zos bridge" + }, + "is_dual": { + "type": "boolean", + "description": "Set to true if br-pub is connected to a physical nic" + }, + "dual_interface": { + "type": "string", + "description": "Set to the physical interface name if is_dual is true" + } + } + }, + + "Workload": { + "type": "object", + "description": "Individual workload specification within a deployment", + "properties": { + "version": { + "type": "integer", + "description": "Version number of the workload specification" + }, + "name": { + "type": "string", + "description": "Unique name for the workload within the deployment" + }, + "type": { + "type": "string", + "description": "Type of workload (e.g., 'zmachine', 'zdb', 'network', 'volume', 'kubernetes')" + }, + "data": { + "type": "object", + "description": "Workload-specific configuration data, varies by workload type" + }, + "metadata": { + "type": "string", + "description": "Optional metadata string for the workload" + }, + "description": { + "type": "string", + "description": "Human-readable description of the workload" + }, + "result": { + "$ref": "#/components/schemas/WorkloadResult", + "description": "Result of workload deployment (populated after deployment)" + } + }, + "required": ["version", "name", "type", "data"] + }, + "WorkloadResult": { + "type": "object", + "description": "Result of workload deployment including status and output data", + "properties": { + "created": { + "type": "integer", + "description": "Unix timestamp when the workload was created" + }, + "state": { + "type": "string", + "description": "Current state of the workload (e.g., 'ok', 'error', 'deleted')" + }, + "message": { + "type": "string", + "description": "Status message or error description" + }, + "data": { + "type": "object", + "description": "Workload-specific result data, varies by workload type" + } + }, + "required": ["created", "state"] + }, + "Deployment": { + "type": "object", + "description": "Complete deployment specification containing workloads, metadata, and signature requirements", + "properties": { + "version": { + "type": "integer", + "description": "Version number of the deployment specification" + }, + "twin_id": { + "type": "integer", + "description": "Twin ID of the deployment owner" + }, + "contract_id": { + "type": "integer", + "description": "Unique contract ID associated with this deployment" + }, + "metadata": { + "type": "string", + "description": "Optional metadata string for the deployment" + }, + "description": { + "type": "string", + "description": "Human-readable description of the deployment" + }, + "expiration": { + "type": "integer", + "description": "Unix timestamp when the deployment expires" + }, + "workloads": { + "type": "array", + "description": "Array of workloads to be deployed", + "items": { + "$ref": "#/components/schemas/Workload" + } + }, + "signature_requirement": { + "$ref": "#/components/schemas/SignatureRequirement", + "description": "Signature requirements for deployment validation" + } + }, + "required": ["version", "twin_id", "contract_id", "workloads"] + }, + "SignatureRequirement": { + "type": "object", + "properties": { + "requests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignatureRequest" + } + }, + "weight_required": { + "type": "integer" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Signature" + } + }, + "signature_style": { + "type": "string" + } + } + }, + "SignatureRequest": { + "type": "object", + "properties": { + "weight": { + "type": "integer" + }, + "twin_id": { + "type": "integer" + }, + "required": { + "type": "boolean" + } + } + }, + "Signature": { + "type": "object", + "properties": { + "twin_id": { + "type": "integer" + }, + "signature": { + "type": "string" + }, + "signature_type": { + "type": "string" + } + } + }, + "Location": { + "type": "object", + "description": "Geographical location information for the node obtained via GeoIP services", + "properties": { + "country": { + "type": "string", + "description": "Country name where the node is located" + }, + "city": { + "type": "string", + "description": "City name where the node is located" + }, + "longitude": { + "type": "number", + "description": "Longitude coordinate of the node location" + }, + "latitude": { + "type": "number", + "description": "Latitude coordinate of the node location" + }, + "continent": { + "type": "string", + "description": "Continent where the node is located" + }, + "region_name": { + "type": "string", + "description": "Region or state name where the node is located" + }, + "region_code": { + "type": "string", + "description": "Region or state code (e.g., 'NY' for New York)" + } + }, + "required": ["country", "city", "longitude", "latitude", "continent"] + } + } + } +} diff --git a/pkg/api/README.md b/pkg/api/README.md new file mode 100644 index 00000000..c14635bd --- /dev/null +++ b/pkg/api/README.md @@ -0,0 +1,234 @@ +# ZOS API Package + +The ZOS API package provides a transport-agnostic interface for interacting with ZOS (Zero OS) nodes. This package is designed with a clear separation between API definitions and transport implementations, ensuring flexibility and maintainability. + +## Overview + +The ZOS API package serves as the core interface layer for ZOS node operations. It provides a comprehensive set of methods for: + +- System information and diagnostics +- Network configuration and monitoring +- Deployment management (create, update, delete, query) +- Storage pool management +- Performance monitoring and benchmarking + +## Transport Agnostic Design + +The API package is designed to be **transport-layer agnostic**, meaning it doesn't depend on specific transport protocols (RPC, REST, WebSocket, etc.). This design principle ensures: + +### Key Benefits + +1. **Protocol Independence**: The core API logic is separated from transport concerns +2. **Clear Interfaces**: Well-defined parameter and return types that work with any transport +4. **Future Flexibility**: New transport protocols can be added without changing core API logic +5. **Maintainability**: Changes to transport don't affect API business logic + +### Architecture + +``` +┌─────────────────────────────────────────┐ +│ Transport Layer │ +│ (JSON-RPC, REST, gRPC, WebSocket...) │ +├─────────────────────────────────────────┤ +│ API Package │ +│ • Method definitions │ +│ • Parameter/Return types │ +│ • Business logic │ +│ • Mode handling (full/light) │ +├─────────────────────────────────────────┤ +│ Service Layer │ +│ • ZBus stubs │ +│ • Resource oracle │ +│ • Diagnostics manager │ +└─────────────────────────────────────────┘ +``` + + +## Operating Modes + +either passing `light` or anything else will considered full/normal zos mode + +## Current Protocol Support + +### JSON-RPC Implementation + +The primary transport protocol currently supported is **JSON-RPC**, implemented in the `pkg/api/jsonrpc` directory. + +#### Handler Registration + +The JSON-RPC handlers are registered in `pkg/api/jsonrpc/handlers.go`: + +```go +func RegisterHandlers(s *messenger.JSONRPCServer, r *RpcHandler) { + // System endpoints + s.RegisterHandler("system.version", r.handleSystemVersion) + s.RegisterHandler("system.dmi", r.handleSystemDMI) + + // Network endpoints + s.RegisterHandler("network.interfaces", r.handleNetworkInterfaces) + + // Deployment endpoints + s.RegisterHandler("deployment.deploy", r.handleDeploymentDeploy) + + // ... and many more +} +``` + +#### Integration Pattern + +1. **RpcHandler**: Wraps the API instance and provides JSON-RPC specific handling +2. **Parameter Extraction**: Converts JSON-RPC parameters to Go types +3. **Method Delegation**: Calls the appropriate API method +4. **Response Formatting**: Returns results in JSON-RPC format + +This pattern allows the core API to remain transport-agnostic while providing a clean JSON-RPC interface. + +## API Documentation Using the OpenRPC Specification + +The `openrpc.json` file can be used with various tools: + +- **API Documentation Generators**: Generate human-readable docs +- **Client Code Generation**: Auto-generate client libraries +- **API Testing Tools**: Validate requests and responses +- **IDE Integration**: Provide autocomplete and validation + +Example method definition from `openrpc.json`: +```json +{ + "name": "system.version", + "params": [], + "result": { + "name": "Version", + "schema": { + "$ref": "#/components/schemas/Version" + } + } +} +``` + +## Usage Examples + +### Basic API Initialization + +```go +package main + +import ( + "context" + "log" + + "github.com/threefoldtech/zbus" + "github.com/threefoldtech/zosbase/pkg/api" +) + +func main() { + // Initialize ZBus client + client, err := zbus.NewClient("unix:///var/run/redis.sock") + if err != nil { + log.Fatal(err) + } + defer client.Close() + + // Create API instance in full mode + apiInstance, err := api.NewAPI(client, "tcp://localhost:6379", "") + if err != nil { + log.Fatal(err) + } + + // Create API instance in light mode + lightAPI, err := api.NewAPI(client, "tcp://localhost:6379", "light") + if err != nil { + log.Fatal(err) + } + + // Create JSON-RPC server + server := messenger.NewJSONRPCServer() + + // Create RPC handler + rpcHandler := jsonrpc.NewRpcHandler(apiInstance) + + // Register all handlers + jsonrpc.RegisterHandlers(server, rpcHandler) + + server.Start(ctx) +} +``` + +## Available API Commands + +The following commands are available through the API. For complete parameter and return type specifications, refer to the `openrpc.json` file. + +### System Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `system.version` | Get ZOS and Zinit versions | None | Version object | +| `system.dmi` | Get DMI (Desktop Management Interface) information | None | DMI object | +| `system.hypervisor` | Detect hypervisor type | None | String | +| `system.diagnostics` | Get comprehensive system diagnostics | None | Diagnostics object | +| `system.features` | Get supported node features | None | NodeFeatures array | + +### Network Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `network.wg_ports` | List reserved WireGuard ports | None | Array of port numbers | +| `network.public_config` | Get public network configuration | None | PublicConfig object | +| `network.has_ipv6` | Check IPv6 support | None | Boolean | +| `network.public_ips` | List public IP addresses | None | Array of IP addresses | +| `network.private_ips` | List private IPs for network | `network_name` | Array of IP addresses | +| `network.interfaces` | List network interfaces | None | Map of interface names to IPs | +| `network.set_public_nic` | Set public network interface | `device` | Success/Error | +| `network.get_public_nic` | Get current public interface | None | Interface name | +| `network.admin.interfaces` | List admin interfaces | None | Interface information | + +### Deployment Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `deployment.deploy` | Deploy new workload | Deployment object | Success/Error | +| `deployment.update` | Update existing deployment | Deployment object | Success/Error | +| `deployment.get` | Get deployment by contract ID | `contract_id` | Deployment object | +| `deployment.list` | List all deployments | None | Deployments array | +| `deployment.changes` | Get deployment changes | `contract_id` | Workloads array | +| `deployment.delete` | Delete deployment | `contract_id` | Success/Error | + +### Performance Monitoring Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `monitor.speed` | Run network speed test | None | Speed test results | +| `monitor.health` | Run health check | None | Health check results | +| `monitor.publicip` | Test public IP connectivity | None | Public IP test results | +| `monitor.benchmark` | Run CPU benchmark | None | Benchmark results | +| `monitor.all` | Run all performance tests | None | Combined test results | + +### Storage Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `storage.pools` | Get storage pool metrics | None | Pool metrics | + +### GPU Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `gpu.list` | List available GPUs | None | GPU information array | + +### Statistics Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `statistics` | Get node resource statistics | None | Statistics object | + +### Location Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `location.get` | Get node location information | None | Location object | + +### VM Log Commands + +| Command | Description | Parameters | Returns | +|---------|-------------|------------|---------| +| `vm.logs` | Get VM log content | `file_name` | Log content string | diff --git a/pkg/api/api.go b/pkg/api/api.go new file mode 100644 index 00000000..6a886e3e --- /dev/null +++ b/pkg/api/api.go @@ -0,0 +1,71 @@ +package api + +import ( + "errors" + "time" + + "github.com/patrickmn/go-cache" + "github.com/threefoldtech/zbus" + "github.com/threefoldtech/zosbase/pkg/capacity" + "github.com/threefoldtech/zosbase/pkg/diagnostics" + "github.com/threefoldtech/zosbase/pkg/stubs" +) + +const ( + cacheDefaultExpiration = 24 * time.Hour + cacheDefaultCleanup = 24 * time.Hour + lightMode = "light" +) + +var ( + ErrNotSupportedInLightMode = errors.New("method is not supported in light mode") +) + +type API struct { + mode string + oracle *capacity.ResourceOracle + versionMonitorStub *stubs.VersionMonitorStub + systemMonitorStub *stubs.SystemMonitorStub + provisionStub *stubs.ProvisionStub + networkerStub *stubs.NetworkerStub + networkerLightStub *stubs.NetworkerLightStub + statisticsStub *stubs.StatisticsStub + storageStub *stubs.StorageModuleStub + performanceMonitorStub *stubs.PerformanceMonitorStub + diagnosticsManager *diagnostics.DiagnosticsManager + inMemCache *cache.Cache +} + +func NewAPI(client zbus.Client, msgBrokerCon string, mode string) (*API, error) { + diagnosticsManager, err := diagnostics.NewDiagnosticsManager(msgBrokerCon, client) + if err != nil { + return nil, err + } + + storageModuleStub := stubs.NewStorageModuleStub(client) + + api := &API{ + mode: mode, + storageStub: storageModuleStub, + diagnosticsManager: diagnosticsManager, + oracle: capacity.NewResourceOracle(storageModuleStub), + versionMonitorStub: stubs.NewVersionMonitorStub(client), + systemMonitorStub: stubs.NewSystemMonitorStub(client), + provisionStub: stubs.NewProvisionStub(client), + statisticsStub: stubs.NewStatisticsStub(client), + performanceMonitorStub: stubs.NewPerformanceMonitorStub(client), + } + + if api.isLightMode() { + api.networkerLightStub = stubs.NewNetworkerLightStub(client) + } else { + api.networkerStub = stubs.NewNetworkerStub(client) + } + + api.inMemCache = cache.New(cacheDefaultExpiration, cacheDefaultCleanup) + return api, nil +} + +func (a *API) isLightMode() bool { + return a.mode == lightMode +} diff --git a/pkg/api/deployment.go b/pkg/api/deployment.go new file mode 100644 index 00000000..04fb212d --- /dev/null +++ b/pkg/api/deployment.go @@ -0,0 +1,53 @@ +package api + +import ( + "context" + "errors" + + "github.com/threefoldtech/tfgrid-sdk-go/messenger" + "github.com/threefoldtech/zosbase/pkg/gridtypes" +) + +func (a *API) DeploymentDeployHandler(ctx context.Context, deployment gridtypes.Deployment) error { + twinID, ok := ctx.Value(messenger.TwinIdContextKey).(uint32) + if !ok { + return errors.New("could not get twin_id from context") + } + return a.provisionStub.CreateOrUpdate(ctx, twinID, deployment, false) +} + +func (a *API) DeploymentUpdateHandler(ctx context.Context, deployment gridtypes.Deployment) error { + twinID, ok := ctx.Value(messenger.TwinIdContextKey).(uint32) + if !ok { + return errors.New("could not get twin_id from context") + } + return a.provisionStub.CreateOrUpdate(ctx, twinID, deployment, true) +} + +func (a *API) DeploymentDeleteHandler(ctx context.Context, contractID uint64) error { + return errors.New("deletion over the api is disabled, please cancel your contract instead") +} + +func (a *API) DeploymentGetHandler(ctx context.Context, contractID uint64) (gridtypes.Deployment, error) { + twinID, ok := ctx.Value(messenger.TwinIdContextKey).(uint32) + if !ok { + return gridtypes.Deployment{}, errors.New("could not get twin_id from context") + } + return a.provisionStub.Get(ctx, twinID, contractID) +} + +func (a *API) DeploymentListHandler(ctx context.Context) ([]gridtypes.Deployment, error) { + twinID, ok := ctx.Value(messenger.TwinIdContextKey).(uint32) + if !ok { + return nil, errors.New("could not get twin_id from context") + } + return a.provisionStub.List(ctx, twinID) +} + +func (a *API) DeploymentChangesHandler(ctx context.Context, contractID uint64) ([]gridtypes.Workload, error) { + twinID, ok := ctx.Value(messenger.TwinIdContextKey).(uint32) + if !ok { + return nil, errors.New("could not get twin_id from context") + } + return a.provisionStub.Changes(ctx, twinID, contractID) +} diff --git a/pkg/api/jsonrpc/handlers.go b/pkg/api/jsonrpc/handlers.go new file mode 100644 index 00000000..065230f1 --- /dev/null +++ b/pkg/api/jsonrpc/handlers.go @@ -0,0 +1,251 @@ +package jsonrpc + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/threefoldtech/tfgrid-sdk-go/messenger" + "github.com/threefoldtech/zosbase/pkg/api" + "github.com/threefoldtech/zosbase/pkg/gridtypes" +) + +type RpcHandler struct { + api *api.API +} + +// NewRpcHandler creates a new RpcHandler instance with the provided API. +func NewRpcHandler(api *api.API) *RpcHandler { + return &RpcHandler{ + api: api, + } +} + +func RegisterHandlers(s *messenger.JSONRPCServer, r *RpcHandler) { + s.RegisterHandler("system.version", r.handleSystemVersion) + s.RegisterHandler("system.dmi", r.handleSystemDMI) + s.RegisterHandler("system.hypervisor", r.handleSystemHypervisor) + s.RegisterHandler("system.diagnostics", r.handleSystemDiagnostics) + s.RegisterHandler("system.features", r.handleSystemNodeFeatures) + + s.RegisterHandler("monitor.speed", r.handlePerfSpeed) + s.RegisterHandler("monitor.health", r.handlePerfHealth) + s.RegisterHandler("monitor.publicip", r.handlePerfPublicIp) + s.RegisterHandler("monitor.benchmark", r.handlePerfBenchmark) + s.RegisterHandler("monitor.all", r.handlePerfAll) + + s.RegisterHandler("network.wg_ports", r.handleNetworkWGPorts) + s.RegisterHandler("network.public_config", r.handleNetworkPublicConfig) + s.RegisterHandler("network.has_ipv6", r.handleNetworkHasIPv6) + s.RegisterHandler("network.public_ips", r.handleNetworkPublicIPs) + s.RegisterHandler("network.private_ips", r.handleNetworkPrivateIPs) + s.RegisterHandler("network.interfaces", r.handleNetworkInterfaces) + s.RegisterHandler("network.set_public_nic", r.handleAdminSetPublicNIC) + s.RegisterHandler("network.get_public_nic", r.handleAdminGetPublicNIC) + s.RegisterHandler("network.admin.interfaces", r.handleAdminInterfaces) + + s.RegisterHandler("deployment.deploy", r.handleDeploymentDeploy) + s.RegisterHandler("deployment.update", r.handleDeploymentUpdate) + s.RegisterHandler("deployment.get", r.handleDeploymentGet) + s.RegisterHandler("deployment.list", r.handleDeploymentList) + s.RegisterHandler("deployment.changes", r.handleDeploymentChanges) + s.RegisterHandler("deployment.delete", r.handleDeploymentDelete) + + s.RegisterHandler("gpu.list", r.handleGpuList) + s.RegisterHandler("storage.pools", r.handleStoragePools) + s.RegisterHandler("statistics", r.handleStatistics) + s.RegisterHandler("location.get", r.handleLocationGet) + s.RegisterHandler("vm.logs", r.handleVmLogs) + +} + +func extractObject(params json.RawMessage, obj interface{}) error { + if err := json.Unmarshal(params, obj); err != nil { + return fmt.Errorf("invalid object parameter: %w", err) + } + return nil +} + +func (r *RpcHandler) handleSystemVersion(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.SystemVersion(ctx) +} + +func (r *RpcHandler) handleSystemDMI(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.SystemDMI(ctx) +} + +func (r *RpcHandler) handleSystemHypervisor(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.SystemHypervisor(ctx) +} + +func (r *RpcHandler) handleSystemDiagnostics(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.SystemDiagnostics(ctx) +} + +func (r *RpcHandler) handleSystemNodeFeatures(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.SystemNodeFeatures(ctx), nil +} + +func (r *RpcHandler) handlePerfSpeed(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.PerfSpeed(ctx) +} + +func (r *RpcHandler) handlePerfHealth(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.PerfHealth(ctx) +} + +func (r *RpcHandler) handlePerfPublicIp(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.PerfPublicIp(ctx) +} + +func (r *RpcHandler) handlePerfBenchmark(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.PerfBenchmark(ctx) +} + +func (r *RpcHandler) handlePerfAll(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.PerfAll(ctx) +} + +func (r *RpcHandler) handleGpuList(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.GpuList(ctx) +} + +func (r *RpcHandler) handleStoragePools(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.StoragePoolsHandler(ctx) +} + +func (r *RpcHandler) handleNetworkWGPorts(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.NetworkWGPorts(ctx) +} + +func (r *RpcHandler) handleNetworkPublicConfig(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.NetworkPublicConfigGet(ctx, nil) +} + +func (r *RpcHandler) handleNetworkHasIPv6(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.NetworkHasIPv6(ctx) +} + +func (r *RpcHandler) handleNetworkPublicIPs(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.NetworkListPublicIPs(ctx) +} + +func (r *RpcHandler) handleNetworkPrivateIPs(ctx context.Context, params json.RawMessage) (interface{}, error) { + type networkParams struct { + NetworkName string `json:"network_name"` + } + + var p networkParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return r.api.NetworkListPrivateIPs(ctx, p.NetworkName) +} + +func (r *RpcHandler) handleNetworkInterfaces(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.NetworkInterfaces(ctx) +} + +func (r *RpcHandler) handleAdminInterfaces(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.AdminInterfaces(ctx) +} + +func (r *RpcHandler) handleAdminSetPublicNIC(ctx context.Context, params json.RawMessage) (interface{}, error) { + type deviceParams struct { + Device string `json:"device"` + } + + var p deviceParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return nil, r.api.AdminSetPublicNIC(ctx, p.Device) +} + +func (r *RpcHandler) handleAdminGetPublicNIC(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.AdminGetPublicNIC(ctx) +} + +func (r *RpcHandler) handleDeploymentDeploy(ctx context.Context, params json.RawMessage) (interface{}, error) { + var deployment gridtypes.Deployment + if err := extractObject(params, &deployment); err != nil { + return nil, err + } + + return nil, r.api.DeploymentDeployHandler(ctx, deployment) +} + +func (r *RpcHandler) handleDeploymentUpdate(ctx context.Context, params json.RawMessage) (interface{}, error) { + var deployment gridtypes.Deployment + if err := extractObject(params, &deployment); err != nil { + return nil, err + } + + return nil, r.api.DeploymentUpdateHandler(ctx, deployment) +} + +func (r *RpcHandler) handleDeploymentGet(ctx context.Context, params json.RawMessage) (interface{}, error) { + type contractParams struct { + ContractID uint64 `json:"contract_id"` + } + + var p contractParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return r.api.DeploymentGetHandler(ctx, p.ContractID) +} + +func (r *RpcHandler) handleDeploymentList(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.DeploymentListHandler(ctx) +} + +func (r *RpcHandler) handleDeploymentChanges(ctx context.Context, params json.RawMessage) (interface{}, error) { + type contractParams struct { + ContractID uint64 `json:"contract_id"` + } + + var p contractParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return r.api.DeploymentChangesHandler(ctx, p.ContractID) +} + +func (r *RpcHandler) handleDeploymentDelete(ctx context.Context, params json.RawMessage) (interface{}, error) { + type contractParams struct { + ContractID uint64 `json:"contract_id"` + } + + var p contractParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return nil, r.api.DeploymentDeleteHandler(ctx, p.ContractID) +} + +func (r *RpcHandler) handleStatistics(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.Statistics(ctx) +} + +func (r *RpcHandler) handleVmLogs(ctx context.Context, params json.RawMessage) (interface{}, error) { + type fileParams struct { + FileName string `json:"file_name"` + } + + var p fileParams + if err := extractObject(params, &p); err != nil { + return nil, err + } + + return r.api.GetVmLogsHandler(ctx, p.FileName) +} + +func (r *RpcHandler) handleLocationGet(ctx context.Context, params json.RawMessage) (interface{}, error) { + return r.api.LocationGet(ctx) +} diff --git a/pkg/api/location.go b/pkg/api/location.go new file mode 100644 index 00000000..a059310d --- /dev/null +++ b/pkg/api/location.go @@ -0,0 +1,31 @@ +package api + +import ( + "context" + "fmt" + + "github.com/patrickmn/go-cache" + "github.com/threefoldtech/zosbase/pkg/geoip" +) + +const ( + locationCacheKey = "location" +) + +func (a *API) LocationGet(ctx context.Context) (geoip.Location, error) { + if loc, found := a.inMemCache.Get(locationCacheKey); found { + if loc, ok := loc.(geoip.Location); ok { + return loc, nil + } + + return geoip.Location{}, fmt.Errorf("failed to convert cached location") + } + + loc, err := geoip.Fetch() + if err != nil { + return geoip.Location{}, err + } + a.inMemCache.Set(locationCacheKey, loc, cache.DefaultExpiration) + + return loc, nil +} diff --git a/pkg/api/logs.go b/pkg/api/logs.go new file mode 100644 index 00000000..e842dfb8 --- /dev/null +++ b/pkg/api/logs.go @@ -0,0 +1,20 @@ +package api + +import ( + "context" + "fmt" + "os" + "path/filepath" +) + +// GetVmLogsHandler returns VM logs +func (a *API) GetVmLogsHandler(ctx context.Context, fileName string) (string, error) { + rootPath := "/var/cache/modules/vmd/logs/" + fullPath := filepath.Join(rootPath, fileName) + content, err := os.ReadFile(fullPath) + if err != nil { + return "", fmt.Errorf("failed to read file, path: %s, %w", fullPath, err) + } + + return string(content), nil +} diff --git a/pkg/api/monitor.go b/pkg/api/monitor.go new file mode 100644 index 00000000..82bca99a --- /dev/null +++ b/pkg/api/monitor.go @@ -0,0 +1,27 @@ +package api + +import ( + "context" + + "github.com/threefoldtech/zosbase/pkg" +) + +func (a *API) PerfSpeed(ctx context.Context) (pkg.IperfTaskResult, error) { + return a.performanceMonitorStub.GetIperfTaskResult(ctx) +} + +func (a *API) PerfHealth(ctx context.Context) (pkg.HealthTaskResult, error) { + return a.performanceMonitorStub.GetHealthTaskResult(ctx) +} + +func (a *API) PerfPublicIp(ctx context.Context) (pkg.PublicIpTaskResult, error) { + return a.performanceMonitorStub.GetPublicIpTaskResult(ctx) +} + +func (a *API) PerfBenchmark(ctx context.Context) (pkg.CpuBenchTaskResult, error) { + return a.performanceMonitorStub.GetCpuBenchTaskResult(ctx) +} + +func (a *API) PerfAll(ctx context.Context) (pkg.AllTaskResult, error) { + return a.performanceMonitorStub.GetAllTaskResult(ctx) +} diff --git a/pkg/api/network.go b/pkg/api/network.go new file mode 100644 index 00000000..e4d8b5b3 --- /dev/null +++ b/pkg/api/network.go @@ -0,0 +1,156 @@ +package api + +import ( + "context" + "errors" + "fmt" + "net" + + "github.com/threefoldtech/zosbase/pkg" + "github.com/threefoldtech/zosbase/pkg/gridtypes" +) + +func (a *API) NetworkWGPorts(ctx context.Context) ([]uint, error) { + if a.isLightMode() { + return nil, ErrNotSupportedInLightMode + } + return a.networkerStub.WireguardPorts(ctx) +} + +func (a *API) NetworkPublicConfigGet(ctx context.Context, _ any) (pkg.PublicConfig, error) { + if a.isLightMode() { + return pkg.PublicConfig{}, ErrNotSupportedInLightMode + } + + return a.networkerStub.GetPublicConfig(ctx) +} + +func (a *API) NetworkHasIPv6(ctx context.Context) (bool, error) { + if a.isLightMode() { + return false, nil + } + + ipData, err := a.networkerStub.GetPublicIPv6Subnet(ctx) + hasIP := ipData.IP != nil && err == nil + return hasIP, nil + +} + +func (a *API) NetworkListPublicIPs(ctx context.Context) ([]string, error) { + if a.isLightMode() { + return nil, ErrNotSupportedInLightMode + } + + return a.provisionStub.ListPublicIPs(ctx) +} + +func (a *API) NetworkListPrivateIPs(ctx context.Context, networkName string) ([]string, error) { + name := gridtypes.Name(networkName) + twinID, ok := ctx.Value("twin_id").(uint32) + if !ok { + return nil, errors.New("could not get twin_id from context") + } + return a.provisionStub.ListPrivateIPs(ctx, twinID, name) +} + +// TODO: inner method should return an array directly (after dropping rmb) +func (a *API) NetworkInterfaces(ctx context.Context) ([]pkg.Interface, error) { + if a.isLightMode() { + interfaces, err := a.networkerLightStub.Interfaces(ctx, "zos", "") + if err != nil { + return nil, err + } + + // Convert map to slice + result := make([]pkg.Interface, 0, len(interfaces.Interfaces)) + for name, iface := range interfaces.Interfaces { + // Ensure the name is set correctly + iface.Name = name + result = append(result, iface) + } + return result, nil + } + + ifaces := []struct { + inf string + ns string + rename string + }{ + {"zos", "", "zos"}, + {"nygg6", "ndmz", "ygg"}, + } + + result := []pkg.Interface{} + for _, i := range ifaces { + ips, mac, err := a.networkerStub.Addrs(ctx, i.inf, i.ns) + if err != nil { + return nil, fmt.Errorf("failed to get ips for '%s' interface: %w", i.inf, err) + } + + iface := pkg.Interface{ + Name: i.rename, + Mac: mac, + IPs: []net.IPNet{}, + } + + for _, item := range ips { + ipNet := net.IPNet{ + IP: item, + Mask: nil, + } + iface.IPs = append(iface.IPs, ipNet) + } + + result = append(result, iface) + } + + return result, nil +} + +// TODO: combine NetworkInterfaces and AdminInterfaces into a single method +func (a *API) AdminInterfaces(ctx context.Context) ([]pkg.Interface, error) { + if a.isLightMode() { + interfaces, err := a.networkerLightStub.Interfaces(ctx, "", "") + if err != nil { + return nil, err + } + + // Convert map to slice + result := make([]pkg.Interface, 0, len(interfaces.Interfaces)) + for name, iface := range interfaces.Interfaces { + // Ensure the name is set correctly + iface.Name = name + result = append(result, iface) + } + return result, nil + } + + interfaces, err := a.networkerStub.Interfaces(ctx, "", "") + if err != nil { + return nil, err + } + + // Convert map to slice + result := make([]pkg.Interface, 0, len(interfaces.Interfaces)) + for name, iface := range interfaces.Interfaces { + // Ensure the name is set correctly + iface.Name = name + result = append(result, iface) + } + return result, nil +} + +func (a *API) AdminSetPublicNIC(ctx context.Context, device string) error { + if a.isLightMode() { + return ErrNotSupportedInLightMode + } + return a.networkerStub.SetPublicExitDevice(ctx, device) +} + +func (a *API) AdminGetPublicNIC(ctx context.Context) (pkg.ExitDevice, error) { + if a.isLightMode() { + return pkg.ExitDevice{}, ErrNotSupportedInLightMode + } + + return a.networkerStub.GetPublicExitDevice(ctx) +} diff --git a/pkg/api/statistics.go b/pkg/api/statistics.go new file mode 100644 index 00000000..ff78ecde --- /dev/null +++ b/pkg/api/statistics.go @@ -0,0 +1,15 @@ +package api + +import ( + "context" + + "github.com/threefoldtech/zosbase/pkg" +) + +func (a *API) Statistics(ctx context.Context) (pkg.Counters, error) { + return a.statisticsStub.GetCounters(ctx) +} + +func (a *API) GpuList(ctx context.Context) ([]pkg.GPUInfo, error) { + return a.statisticsStub.ListGPUs(ctx) +} diff --git a/pkg/api/storage.go b/pkg/api/storage.go new file mode 100644 index 00000000..68f87de3 --- /dev/null +++ b/pkg/api/storage.go @@ -0,0 +1,11 @@ +package api + +import ( + "context" + + "github.com/threefoldtech/zosbase/pkg" +) + +func (a *API) StoragePoolsHandler(ctx context.Context) ([]pkg.PoolMetrics, error) { + return a.storageStub.Metrics(ctx) +} diff --git a/pkg/api/system.go b/pkg/api/system.go new file mode 100644 index 00000000..837d303a --- /dev/null +++ b/pkg/api/system.go @@ -0,0 +1,51 @@ +package api + +import ( + "context" + "os/exec" + "strings" + + "github.com/threefoldtech/zosbase/pkg" + "github.com/threefoldtech/zosbase/pkg/capacity/dmi" + "github.com/threefoldtech/zosbase/pkg/diagnostics" +) + +func (a *API) SystemVersion(ctx context.Context) (Version, error) { + output, err := exec.CommandContext(ctx, "zinit", "-V").CombinedOutput() + var zInitVer string + if err != nil { + zInitVer = err.Error() + } else { + zInitVer = strings.TrimSpace(strings.TrimPrefix(string(output), "zinit")) + } + + version := Version{ + Zos: a.versionMonitorStub.GetVersion(ctx).String(), + Zinit: zInitVer, + } + + return version, nil +} + +// TODO: we should provide a better parsing for this to send over api +// not all the fields are needed, and some of them are not even used +func (a *API) SystemDMI(ctx context.Context) (dmi.DMI, error) { + dmiData, err := a.oracle.DMI() + if err != nil { + return dmi.DMI{}, err + } + + return *dmiData, nil +} + +func (a *API) SystemHypervisor(ctx context.Context) (string, error) { + return a.oracle.GetHypervisor() +} + +func (a *API) SystemDiagnostics(ctx context.Context) (diagnostics.Diagnostics, error) { + return a.diagnosticsManager.GetSystemDiagnostics(ctx) +} + +func (a *API) SystemNodeFeatures(ctx context.Context) []pkg.NodeFeature { + return a.systemMonitorStub.GetNodeFeatures(ctx) +} diff --git a/pkg/api/types.go b/pkg/api/types.go new file mode 100644 index 00000000..3c065732 --- /dev/null +++ b/pkg/api/types.go @@ -0,0 +1,7 @@ +package api + +// Version represents the version of the node +type Version struct { + Zos string `json:"zos"` + Zinit string `json:"zinit"` +} diff --git a/pkg/perf/cache.go b/pkg/perf/cache.go index b9d6d237..0b589ecc 100644 --- a/pkg/perf/cache.go +++ b/pkg/perf/cache.go @@ -1,42 +1,98 @@ package perf import ( - "context" "encoding/json" - "fmt" "github.com/garyburd/redigo/redis" "github.com/pkg/errors" "github.com/threefoldtech/zosbase/pkg" ) -const ( - moduleName = "perf" -) +func (pm *PerformanceMonitor) GetIperfTaskResult() (pkg.IperfTaskResult, error) { + report, err := pm.get(iperfTaskName) + if err != nil { + return pkg.IperfTaskResult{}, errors.Wrap(err, "failed to get iperf task result") + } -var ( - ErrResultNotFound = errors.New("result not found") -) + var result pkg.IperfTaskResult + if err := json.Unmarshal(report, &result); err != nil { + return pkg.IperfTaskResult{}, errors.Wrap(err, "failed to unmarshal iperf task result") + } -// generateKey is helper method to add moduleName as prefix for the taskName -func generateKey(taskName string) string { - return fmt.Sprintf("%s.%s", moduleName, taskName) + return result, nil } -// setCache set result in redis -func (pm *PerformanceMonitor) setCache(ctx context.Context, result pkg.TaskResult) error { - data, err := json.Marshal(result) +func (pm *PerformanceMonitor) GetHealthTaskResult() (pkg.HealthTaskResult, error) { + report, err := pm.get(healthCheckTaskName) if err != nil { - return errors.Wrap(err, "failed to marshal data to JSON") + return pkg.HealthTaskResult{}, errors.Wrap(err, "failed to get health check task result") } - conn := pm.pool.Get() - defer conn.Close() + var result pkg.HealthTaskResult + if err := json.Unmarshal(report, &result); err != nil { + return pkg.HealthTaskResult{}, errors.Wrap(err, "failed to unmarshal health check task result") + } + return result, nil +} + +func (pm *PerformanceMonitor) GetPublicIpTaskResult() (pkg.PublicIpTaskResult, error) { + report, err := pm.get(publicIpTaskName) + if err != nil { + return pkg.PublicIpTaskResult{}, errors.Wrap(err, "failed to get public IP task result") + } + + var result pkg.PublicIpTaskResult + if err := json.Unmarshal(report, &result); err != nil { + return pkg.PublicIpTaskResult{}, errors.Wrap(err, "failed to unmarshal public IP task result") + } + return result, nil +} + +func (pm *PerformanceMonitor) GetCpuBenchTaskResult() (pkg.CpuBenchTaskResult, error) { + var result pkg.CpuBenchTaskResult + report, err := pm.get(cpuBenchmarkTaskName) + if err != nil { + return pkg.CpuBenchTaskResult{}, errors.Wrap(err, "failed to get CPU benchmark task result") + } + + if err := json.Unmarshal(report, &result); err != nil { + return pkg.CpuBenchTaskResult{}, errors.Wrap(err, "failed to unmarshal CPU benchmark task result") + } + return result, nil +} - _, err = conn.Do("SET", generateKey(result.Name), data) - return err +func (pm *PerformanceMonitor) GetAllTaskResult() (pkg.AllTaskResult, error) { + var results pkg.AllTaskResult + + cpuResult, err := pm.GetCpuBenchTaskResult() + if err != nil { + return pkg.AllTaskResult{}, errors.Wrap(err, "failed to get CPU benchmark result") + } + results.CpuBenchmark = cpuResult + + healthResult, err := pm.GetHealthTaskResult() + if err != nil { + return pkg.AllTaskResult{}, errors.Wrap(err, "failed to get health check result") + } + results.HealthCheck = healthResult + + iperfResult, err := pm.GetIperfTaskResult() + if err != nil { + return pkg.AllTaskResult{}, errors.Wrap(err, "failed to get iperf result") + } + results.Iperf = iperfResult + + publicIpResult, err := pm.GetPublicIpTaskResult() + if err != nil { + return pkg.AllTaskResult{}, errors.Wrap(err, "failed to get public IP result") + } + results.PublicIp = publicIpResult + + return results, nil } +// DEPRECATED + // get directly gets result for some key func get(conn redis.Conn, key string) (pkg.TaskResult, error) { var res pkg.TaskResult @@ -62,7 +118,7 @@ func get(conn redis.Conn, key string) (pkg.TaskResult, error) { func (pm *PerformanceMonitor) Get(taskName string) (pkg.TaskResult, error) { conn := pm.pool.Get() defer conn.Close() - return get(conn, generateKey(taskName)) + return get(conn, generatePerfKey(taskName)) } // GetAll gets the results for all the tests with moduleName as prefix @@ -76,7 +132,7 @@ func (pm *PerformanceMonitor) GetAll() ([]pkg.TaskResult, error) { cursor := 0 for { - values, err := redis.Values(conn.Do("SCAN", cursor, "MATCH", generateKey("*"))) + values, err := redis.Values(conn.Do("SCAN", cursor, "MATCH", generatePerfKey("*"))) if err != nil { return nil, err } @@ -101,15 +157,3 @@ func (pm *PerformanceMonitor) GetAll() ([]pkg.TaskResult, error) { } return res, nil } - -// exists check if a key exists -func (pm *PerformanceMonitor) exists(key string) (bool, error) { - conn := pm.pool.Get() - defer conn.Close() - - ok, err := redis.Bool(conn.Do("EXISTS", generateKey(key))) - if err != nil { - return false, errors.Wrapf(err, "error checking if key %s exists", generateKey(key)) - } - return ok, nil -} diff --git a/pkg/perf/cache_utils.go b/pkg/perf/cache_utils.go new file mode 100644 index 00000000..de78ef3d --- /dev/null +++ b/pkg/perf/cache_utils.go @@ -0,0 +1,72 @@ +package perf + +import ( + "encoding/json" + "fmt" + + "github.com/garyburd/redigo/redis" + "github.com/pkg/errors" + "github.com/threefoldtech/zosbase/pkg" +) + +const ( + moduleName = "perf" + healthCheckTaskName = "healthcheck" + iperfTaskName = "iperf" + publicIpTaskName = "public-ip-validation" + cpuBenchmarkTaskName = "cpu-benchmark" +) + +var ( + ErrResultNotFound = errors.New("result not found") +) + +func generatePerfKey(taskName string) string { + return fmt.Sprintf("%s.%s", moduleName, taskName) +} + +func (pm *PerformanceMonitor) exists(key string) (bool, error) { + conn := pm.pool.Get() + defer conn.Close() + key = generatePerfKey(key) + + ok, err := redis.Bool(conn.Do("EXISTS", key)) + if err != nil { + return false, errors.Wrapf(err, "error checking if key %s exists", key) + } + return ok, nil +} + +func (pm *PerformanceMonitor) get(key string) ([]byte, error) { + conn := pm.pool.Get() + defer conn.Close() + key = generatePerfKey(key) + + data, err := redis.Bytes(conn.Do("GET", key)) + if err != nil { + if err == redis.ErrNil { + return nil, ErrResultNotFound + } + return nil, errors.Wrap(err, "failed to get the result") + } + + if data == nil { + return nil, ErrResultNotFound + } + + return data, nil +} + +func (pm *PerformanceMonitor) set(result pkg.TaskResult) error { + conn := pm.pool.Get() + defer conn.Close() + key := generatePerfKey(result.Name) + + data, err := json.Marshal(result) + if err != nil { + return errors.Wrap(err, "failed to marshal data to JSON") + } + + _, err = conn.Do("SET", key, data) + return err +} diff --git a/pkg/perf/monitor.go b/pkg/perf/monitor.go index 3d47ea38..87f471cd 100644 --- a/pkg/perf/monitor.go +++ b/pkg/perf/monitor.go @@ -63,7 +63,7 @@ func (pm *PerformanceMonitor) runTask(ctx context.Context, task Task) error { return errors.Wrapf(err, "failed to run task: %s", task.ID()) } - err = pm.setCache(ctx, pkg.TaskResult{ + err = pm.set(pkg.TaskResult{ Name: task.ID(), Timestamp: uint64(time.Now().Unix()), Description: task.Description(), diff --git a/pkg/performance_monitor.go b/pkg/performance_monitor.go index fef8b413..4bcc6546 100644 --- a/pkg/performance_monitor.go +++ b/pkg/performance_monitor.go @@ -3,6 +3,12 @@ package pkg //go:generate zbusc -module node -version 0.0.1 -name performance-monitor -package stubs github.com/threefoldtech/zosbase/pkg+PerformanceMonitor stubs/performance_monitor_stub.go type PerformanceMonitor interface { + GetAllTaskResult() (AllTaskResult, error) + GetIperfTaskResult() (IperfTaskResult, error) + GetHealthTaskResult() (HealthTaskResult, error) + GetPublicIpTaskResult() (PublicIpTaskResult, error) + GetCpuBenchTaskResult() (CpuBenchTaskResult, error) + // Deprecated Get(taskName string) (TaskResult, error) GetAll() ([]TaskResult, error) } @@ -14,3 +20,86 @@ type TaskResult struct { Timestamp uint64 `json:"timestamp"` Result interface{} `json:"result"` } + +// CPUUtilizationPercent represents CPU utilization percentages +type CPUUtilizationPercent struct { + Host float64 `json:"host_total"` + HostUser float64 `json:"host_user"` + HostSystem float64 `json:"host_system"` + RemoteTotal float64 `json:"remote_total"` + RemoteUser float64 `json:"remote_user"` + RemoteSystem float64 `json:"remote_system"` + RemoteTotalMessage float64 `json:"remote_total_message"` + RemoteUserMessage float64 `json:"remote_user_message"` + RemoteSystemMessage float64 `json:"remote_system_message"` +} + +// IperfResult for iperf test results +type IperfResult struct { + UploadSpeed float64 `json:"upload_speed"` // in bit/sec + DownloadSpeed float64 `json:"download_speed"` // in bit/sec + NodeID uint32 `json:"node_id"` + NodeIpv4 string `json:"node_ip"` + TestType string `json:"test_type"` + Error string `json:"error"` + CpuReport CPUUtilizationPercent `json:"cpu_report"` +} + +type TaskResultBase struct { + Name string `json:"name"` + Description string `json:"description"` + Timestamp uint64 `json:"timestamp"` +} + +// IperfTaskResult represents the result of an iperf task +type IperfTaskResult struct { + TaskResultBase + Result []IperfResult `json:"result"` +} + +// HealthReport represents a health check report +type HealthReport struct { + TestName string `json:"test_name"` + Errors []string `json:"errors"` +} + +// HealthTaskResult represents the result of a health check task +type HealthTaskResult struct { + TaskResultBase + Result HealthReport `json:"result"` +} + +// Report represents a public IP validation report +type Report struct { + Ip string `json:"ip"` + State string `json:"state"` + Reason string `json:"reason"` +} + +// PublicIpTaskResult represents the result of a public IP validation task +type PublicIpTaskResult struct { + TaskResultBase + Result []Report `json:"result"` +} + +// CPUBenchmarkResult holds CPU benchmark results +type CPUBenchmarkResult struct { + SingleThreaded float64 `json:"single"` + MultiThreaded float64 `json:"multi"` + Threads int `json:"threads"` + Workloads int `json:"workloads"` +} + +// CpuBenchTaskResult represents the result of a CPU benchmark task +type CpuBenchTaskResult struct { + TaskResultBase + Result CPUBenchmarkResult `json:"result"` +} + +// AllTaskResult represents all task results combined +type AllTaskResult struct { + CpuBenchmark CpuBenchTaskResult `json:"cpu_benchmark"` + HealthCheck HealthTaskResult `json:"health_check"` + Iperf IperfTaskResult `json:"iperf"` + PublicIp PublicIpTaskResult `json:"public_ip"` +} diff --git a/pkg/stubs/performance_monitor_stub.go b/pkg/stubs/performance_monitor_stub.go index 003ac849..f6c486cd 100644 --- a/pkg/stubs/performance_monitor_stub.go +++ b/pkg/stubs/performance_monitor_stub.go @@ -60,3 +60,88 @@ func (s *PerformanceMonitorStub) GetAll(ctx context.Context) (ret0 []pkg.TaskRes } return } + +func (s *PerformanceMonitorStub) GetAllTaskResult(ctx context.Context) (ret0 pkg.AllTaskResult, ret1 error) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "GetAllTaskResult", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + ret1 = result.CallError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +} + +func (s *PerformanceMonitorStub) GetCpuBenchTaskResult(ctx context.Context) (ret0 pkg.CpuBenchTaskResult, ret1 error) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "GetCpuBenchTaskResult", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + ret1 = result.CallError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +} + +func (s *PerformanceMonitorStub) GetHealthTaskResult(ctx context.Context) (ret0 pkg.HealthTaskResult, ret1 error) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "GetHealthTaskResult", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + ret1 = result.CallError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +} + +func (s *PerformanceMonitorStub) GetIperfTaskResult(ctx context.Context) (ret0 pkg.IperfTaskResult, ret1 error) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "GetIperfTaskResult", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + ret1 = result.CallError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +} + +func (s *PerformanceMonitorStub) GetPublicIpTaskResult(ctx context.Context) (ret0 pkg.PublicIpTaskResult, ret1 error) { + args := []interface{}{} + result, err := s.client.RequestContext(ctx, s.module, s.object, "GetPublicIpTaskResult", args...) + if err != nil { + panic(err) + } + result.PanicOnError() + ret1 = result.CallError() + loader := zbus.Loader{ + &ret0, + } + if err := result.Unmarshal(&loader); err != nil { + panic(err) + } + return +}