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); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user