Create JSON-RPC server repo with stats #44
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2484,6 +2484,19 @@ dependencies = [
|
|||||||
"peach-lib",
|
"peach-lib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peach-jsonrpc-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger 0.9.0",
|
||||||
|
"jsonrpc-core 18.0.0",
|
||||||
|
"jsonrpc-http-server 18.0.0",
|
||||||
|
"jsonrpc-test 18.0.0",
|
||||||
|
"log 0.4.14",
|
||||||
|
"miniserde",
|
||||||
|
"peach-stats",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peach-lib"
|
name = "peach-lib"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -2777,7 +2790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216"
|
checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"time 0.1.44",
|
"time 0.2.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -10,6 +10,7 @@ members = [
|
|||||||
"peach-menu",
|
"peach-menu",
|
||||||
"peach-monitor",
|
"peach-monitor",
|
||||||
"peach-stats",
|
"peach-stats",
|
||||||
|
"peach-jsonrpc-server",
|
||||||
"peach-probe",
|
"peach-probe",
|
||||||
"peach-dyndns-updater"
|
"peach-dyndns-updater"
|
||||||
]
|
]
|
||||||
|
25
peach-jsonrpc-server/Cargo.toml
Normal file
25
peach-jsonrpc-server/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "peach-jsonrpc-server"
|
||||||
|
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "JSON-RPC over HTTP for the PeachCloud system. Provides a JSON-RPC wrapper around the stats, network and oled libraries."
|
||||||
|
homepage = "https://opencollective.com/peachcloud"
|
||||||
|
repository = "https://git.coopcloud.tech/PeachCloud/peach-workspace"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.9"
|
||||||
|
jsonrpc-core = "18"
|
||||||
|
jsonrpc-http-server = "18"
|
||||||
|
log = "0.4"
|
||||||
|
miniserde = "0.1.15"
|
||||||
|
peach-stats = { path = "../peach-stats", features = ["miniserde_support"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
jsonrpc-test = "18"
|
72
peach-jsonrpc-server/README.md
Normal file
72
peach-jsonrpc-server/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# peach-jsonrpc-server
|
||||||
|
|
||||||
|
A JSON-RPC server for the PeachCloud system which exposes an API over HTTP.
|
||||||
|
|
||||||
|
Currently includes peach-stats capability (system statistics).
|
||||||
|
|
||||||
|
## JSON-RPC API
|
||||||
|
|
||||||
|
| Method | Description | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
|
||||||
|
| `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
|
||||||
|
| `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
|
||||||
|
| `load_average` | Load average statistics | `one`, `five`, `fifteen` |
|
||||||
|
| `mem_stats` | Memory statistics | `total`, `free`, `used` |
|
||||||
|
| `ping` | Microservice status | `success` if running |
|
||||||
|
| `uptime` | System uptime | `secs` |
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
The JSON-RPC HTTP server is currently hardcoded to run on "127.0.0.1:5110". Address and port configuration settings will later be exposed via CLI arguments and possibly an environment variable.
|
||||||
|
|
||||||
|
Logging is made available with `env_logger`:
|
||||||
|
|
||||||
|
`export RUST_LOG=info`
|
||||||
|
|
||||||
|
Other logging levels include `debug`, `warn` and `error`.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Clone the peach-workspace repo:
|
||||||
|
|
||||||
|
`git clone https://git.coopcloud.tech/PeachCloud/peach-workspace`
|
||||||
|
|
||||||
|
Move into the repo peaach-jsonrpc-server directory and compile a release build:
|
||||||
|
|
||||||
|
`cd peach-jsonrpc-server`
|
||||||
|
`cargo build --release`
|
||||||
|
|
||||||
|
Run the binary:
|
||||||
|
|
||||||
|
`./peach-workspace/target/release/peach-jsonrpc-server`
|
||||||
|
|
||||||
|
## Debian Packaging
|
||||||
|
|
||||||
|
TODO.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
**Get CPU Statistics**
|
||||||
|
|
||||||
|
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "cpu_stats", "id":1 }' 127.0.0.1:5110`
|
||||||
|
|
||||||
|
Server responds with:
|
||||||
|
|
||||||
|
`{"jsonrpc":"2.0","result":"{\"user\":4661083,\"system\":1240371,\"idle\":326838290,\"nice\":0}","id":1}`
|
||||||
|
|
||||||
|
**Get System Uptime**
|
||||||
|
|
||||||
|
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "uptime", "id":1 }' 127.0.0.1:5110`
|
||||||
|
|
||||||
|
Server responds with:
|
||||||
|
|
||||||
|
`{"jsonrpc":"2.0","result":"{\"secs\":840968}","id":1}`
|
||||||
|
|
||||||
|
### Licensing
|
||||||
|
|
||||||
|
AGPL-3.0
|
46
peach-jsonrpc-server/src/error.rs
Normal file
46
peach-jsonrpc-server/src/error.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
||||||
|
use peach_stats::StatsError;
|
||||||
|
|
||||||
|
/// Custom error type encapsulating all possible errors for a JSON-RPC server
|
||||||
|
/// and associated methods.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum JsonRpcServerError {
|
||||||
|
/// An error returned from the `peach-stats` library.
|
||||||
|
Stats(StatsError),
|
||||||
|
/// An expected JSON-RPC method parameter was not provided.
|
||||||
|
MissingParameter(JsonRpcError),
|
||||||
|
/// Failed to parse a provided JSON-RPC method parameter.
|
||||||
|
ParseParameter(JsonRpcError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for JsonRpcServerError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
JsonRpcServerError::ParseParameter(ref source) => {
|
||||||
|
write!(f, "Failed to parse parameter: {}", source)
|
||||||
|
}
|
||||||
|
JsonRpcServerError::MissingParameter(ref source) => {
|
||||||
|
write!(f, "Missing expected parameter: {}", source)
|
||||||
|
}
|
||||||
|
JsonRpcServerError::Stats(ref source) => {
|
||||||
|
write!(f, "{}", source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonRpcServerError> for JsonRpcError {
|
||||||
|
fn from(err: JsonRpcServerError) -> Self {
|
||||||
|
match &err {
|
||||||
|
JsonRpcServerError::Stats(source) => JsonRpcError {
|
||||||
|
code: ErrorCode::ServerError(-32001),
|
||||||
|
message: format!("{}", source),
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
JsonRpcServerError::MissingParameter(source) => source.clone(),
|
||||||
|
JsonRpcServerError::ParseParameter(source) => source.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
peach-jsonrpc-server/src/lib.rs
Normal file
141
peach-jsonrpc-server/src/lib.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
//! # peach-jsonrpc-server
|
||||||
|
//!
|
||||||
|
//! A JSON-RPC server which exposes an API over HTTP.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::result::Result;
|
||||||
|
|
||||||
|
use jsonrpc_core::{IoHandler, Value};
|
||||||
|
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||||
|
use log::info;
|
||||||
|
use miniserde::json;
|
||||||
|
use peach_stats::stats;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
use crate::error::JsonRpcServerError;
|
||||||
|
|
||||||
|
/// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server.
|
||||||
|
pub fn run() -> Result<(), JsonRpcServerError> {
|
||||||
|
info!("Starting up.");
|
||||||
|
|
||||||
|
info!("Creating JSON-RPC I/O handler.");
|
||||||
|
let mut io = IoHandler::default();
|
||||||
|
|
||||||
|
io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
|
||||||
|
|
||||||
|
// TODO: add blocks of methods according to provided flags
|
||||||
|
|
||||||
|
/* PEACH-STATS RPC METHODS */
|
||||||
|
|
||||||
|
io.add_sync_method("cpu_stats", move |_| {
|
||||||
|
info!("Fetching CPU statistics.");
|
||||||
|
let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_cpu = json::to_string(&cpu);
|
||||||
|
|
||||||
|
Ok(Value::String(json_cpu))
|
||||||
|
});
|
||||||
|
|
||||||
|
io.add_sync_method("cpu_stats_percent", move |_| {
|
||||||
|
info!("Fetching CPU statistics as percentages.");
|
||||||
|
let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_cpu = json::to_string(&cpu);
|
||||||
|
|
||||||
|
Ok(Value::String(json_cpu))
|
||||||
|
});
|
||||||
|
|
||||||
|
io.add_sync_method("disk_usage", move |_| {
|
||||||
|
info!("Fetching disk usage statistics.");
|
||||||
|
let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_disks = json::to_string(&disks);
|
||||||
|
|
||||||
|
Ok(Value::String(json_disks))
|
||||||
|
});
|
||||||
|
|
||||||
|
io.add_sync_method("load_average", move |_| {
|
||||||
|
info!("Fetching system load average statistics.");
|
||||||
|
let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_avg = json::to_string(&avg);
|
||||||
|
|
||||||
|
Ok(Value::String(json_avg))
|
||||||
|
});
|
||||||
|
|
||||||
|
io.add_sync_method("mem_stats", move |_| {
|
||||||
|
info!("Fetching current memory statistics.");
|
||||||
|
let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_mem = json::to_string(&mem);
|
||||||
|
|
||||||
|
Ok(Value::String(json_mem))
|
||||||
|
});
|
||||||
|
|
||||||
|
io.add_sync_method("uptime", move |_| {
|
||||||
|
info!("Fetching system uptime.");
|
||||||
|
let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
|
||||||
|
let json_uptime = json::to_string(&uptime);
|
||||||
|
|
||||||
|
Ok(Value::String(json_uptime))
|
||||||
|
});
|
||||||
|
|
||||||
|
let http_server =
|
||||||
|
env::var("PEACH_JSONRPC_SERVER").unwrap_or_else(|_| "127.0.0.1:5110".to_string());
|
||||||
|
|
||||||
|
info!("Starting JSON-RPC server on {}.", http_server);
|
||||||
|
let server = ServerBuilder::new(io)
|
||||||
|
.cors(DomainsValidation::AllowOnly(vec![
|
||||||
|
AccessControlAllowOrigin::Null,
|
||||||
|
]))
|
||||||
|
.start_http(
|
||||||
|
&http_server
|
||||||
|
.parse()
|
||||||
|
.expect("Invalid HTTP address and port combination"),
|
||||||
|
)
|
||||||
|
.expect("Unable to start RPC server");
|
||||||
|
|
||||||
|
server.wait();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
||||||
|
use jsonrpc_test as test_rpc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_success() {
|
||||||
|
let rpc = {
|
||||||
|
let mut io = IoHandler::new();
|
||||||
|
io.add_sync_method("rpc_success_response", |_| {
|
||||||
|
Ok(Value::String("success".into()))
|
||||||
|
});
|
||||||
|
test_rpc::Rpc::from(io)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_parse_error() {
|
||||||
|
let rpc = {
|
||||||
|
let mut io = IoHandler::new();
|
||||||
|
io.add_sync_method("rpc_parse_error", |_| {
|
||||||
|
let e = JsonRpcError {
|
||||||
|
code: ErrorCode::ParseError,
|
||||||
|
message: String::from("Parse error"),
|
||||||
|
data: None,
|
||||||
|
};
|
||||||
|
Err(JsonRpcError::from(JsonRpcServerError::MissingParameter(e)))
|
||||||
|
});
|
||||||
|
test_rpc::Rpc::from(io)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
rpc.request("rpc_parse_error", &()),
|
||||||
|
r#"{
|
||||||
|
"code": -32700,
|
||||||
|
"message": "Parse error"
|
||||||
|
}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
peach-jsonrpc-server/src/main.rs
Normal file
34
peach-jsonrpc-server/src/main.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
//! # peach-jsonrpc-server
|
||||||
|
//!
|
||||||
|
//! A JSON-RPC server which exposes an over HTTP.
|
||||||
|
//!
|
||||||
|
//! Currently includes peach-stats capability (system statistics).
|
||||||
|
//!
|
||||||
|
//! ## API
|
||||||
|
//!
|
||||||
|
//! | Method | Description | Returns |
|
||||||
|
//! | --- | --- | --- |
|
||||||
|
//! | `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
|
||||||
|
//! | `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
|
||||||
|
//! | `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
|
||||||
|
//! | `load_average` | Load average statistics | `one`, `five`, `fifteen` |
|
||||||
|
//! | `mem_stats` | Memory statistics | `total`, `free`, `used` |
|
||||||
|
//! | `ping` | Microservice status | `success` if running |
|
||||||
|
//! | `uptime` | System uptime | `secs` |
|
||||||
|
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// initalize the logger
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
// handle errors returned from `run`
|
||||||
|
if let Err(e) = peach_jsonrpc_server::run() {
|
||||||
|
error!("Application error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user