mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 18:27:56 +00:00
Expose system health via RPC and REST (#1269)
* Implement health endpoint. * Expose health API.
This commit is contained in:
@@ -19,9 +19,16 @@
|
||||
use rpc;
|
||||
|
||||
use errors;
|
||||
use system::helpers::Health;
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
/// Node is not fully functional
|
||||
NotHealthy(h: Health) {
|
||||
description("node is not healthy"),
|
||||
display("Node is not fully functional: {}", h)
|
||||
}
|
||||
|
||||
/// Not implemented yet
|
||||
Unimplemented {
|
||||
description("not yet implemented"),
|
||||
@@ -30,10 +37,17 @@ error_chain! {
|
||||
}
|
||||
}
|
||||
|
||||
const ERROR: i64 = 2000;
|
||||
|
||||
impl From<Error> for rpc::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error(ErrorKind::Unimplemented, _) => errors::unimplemented(),
|
||||
Error(ErrorKind::NotHealthy(h), _) => rpc::Error {
|
||||
code: rpc::ErrorCode::ServerError(ERROR + 1),
|
||||
message: "node is not healthy".into(),
|
||||
data:serde_json::to_value(h).ok(),
|
||||
},
|
||||
e => errors::internal(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate system API helpers.
|
||||
|
||||
use std::fmt;
|
||||
use serde_derive::{Serialize};
|
||||
use serde_json::{Value, map::Map};
|
||||
|
||||
/// Node properties
|
||||
pub type Properties = Map<String, Value>;
|
||||
|
||||
/// Running node's static details.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SystemInfo {
|
||||
/// Implementation name.
|
||||
pub impl_name: String,
|
||||
/// Implementation version.
|
||||
pub impl_version: String,
|
||||
/// Chain name.
|
||||
pub chain_name: String,
|
||||
/// A custom set of properties defined in the chain spec.
|
||||
pub properties: Properties,
|
||||
}
|
||||
|
||||
/// Health struct returned by the RPC
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub struct Health {
|
||||
/// Number of connected peers
|
||||
pub peers: usize,
|
||||
/// Is the node syncing
|
||||
pub is_syncing: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Health {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{} peers ({})", self.peers, if self.is_syncing {
|
||||
"syncing"
|
||||
} else { "idle" })
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,16 @@
|
||||
|
||||
pub mod error;
|
||||
|
||||
mod helpers;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::sync::Arc;
|
||||
use network;
|
||||
use runtime_primitives::traits;
|
||||
|
||||
use self::error::Result;
|
||||
pub use self::helpers::{Properties, SystemInfo, Health};
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Substrate system RPC API
|
||||
@@ -40,6 +46,73 @@ build_rpc_trait! {
|
||||
|
||||
/// Get a custom set of properties as a JSON object, defined in the chain spec.
|
||||
#[rpc(name = "system_properties")]
|
||||
fn system_properties(&self) -> Result<serde_json::map::Map<String, serde_json::Value>>;
|
||||
fn system_properties(&self) -> Result<Properties>;
|
||||
|
||||
/// Return health status of the node.
|
||||
///
|
||||
/// Node is considered healthy if it is:
|
||||
/// - connected to some peers (unless running in dev mode)
|
||||
/// - not performing a major sync
|
||||
#[rpc(name = "system_health")]
|
||||
fn system_health(&self) -> Result<Health>;
|
||||
}
|
||||
}
|
||||
|
||||
/// System API implementation
|
||||
pub struct System<B: traits::Block> {
|
||||
info: SystemInfo,
|
||||
sync: Arc<network::SyncProvider<B>>,
|
||||
should_have_peers: bool,
|
||||
}
|
||||
|
||||
impl<B: traits::Block> System<B> {
|
||||
/// Creates new `System` given the `SystemInfo`.
|
||||
pub fn new(
|
||||
info: SystemInfo,
|
||||
sync: Arc<network::SyncProvider<B>>,
|
||||
should_have_peers: bool,
|
||||
) -> Self {
|
||||
System {
|
||||
info,
|
||||
should_have_peers,
|
||||
sync,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: traits::Block> SystemApi for System<B> {
|
||||
fn system_name(&self) -> Result<String> {
|
||||
Ok(self.info.impl_name.clone())
|
||||
}
|
||||
|
||||
fn system_version(&self) -> Result<String> {
|
||||
Ok(self.info.impl_version.clone())
|
||||
}
|
||||
|
||||
fn system_chain(&self) -> Result<String> {
|
||||
Ok(self.info.chain_name.clone())
|
||||
}
|
||||
|
||||
fn system_properties(&self) -> Result<Properties> {
|
||||
Ok(self.info.properties.clone())
|
||||
}
|
||||
|
||||
fn system_health(&self) -> Result<Health> {
|
||||
let status = self.sync.status();
|
||||
|
||||
let is_syncing = status.sync.is_major_syncing();
|
||||
let peers = status.num_peers;
|
||||
|
||||
let health = Health {
|
||||
peers,
|
||||
is_syncing,
|
||||
};
|
||||
|
||||
let has_no_peers = peers == 0 && self.should_have_peers;
|
||||
if has_no_peers || is_syncing {
|
||||
Err(error::ErrorKind::NotHealthy(health))?
|
||||
} else {
|
||||
Ok(health)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,27 +15,46 @@
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use super::error::*;
|
||||
|
||||
impl SystemApi for () {
|
||||
fn system_name(&self) -> Result<String> {
|
||||
Ok("testclient".into())
|
||||
}
|
||||
fn system_version(&self) -> Result<String> {
|
||||
Ok("0.2.0".into())
|
||||
}
|
||||
fn system_chain(&self) -> Result<String> {
|
||||
Ok("testchain".into())
|
||||
}
|
||||
fn system_properties(&self) -> Result<serde_json::map::Map<String, serde_json::Value>> {
|
||||
Ok(serde_json::map::Map::new())
|
||||
use network::{self, SyncState, SyncStatus, ProtocolStatus};
|
||||
use test_client::runtime::Block;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Status {
|
||||
pub peers: usize,
|
||||
pub is_syncing: bool,
|
||||
pub is_dev: bool,
|
||||
}
|
||||
|
||||
impl network::SyncProvider<Block> for Status {
|
||||
fn status(&self) -> ProtocolStatus<Block> {
|
||||
ProtocolStatus {
|
||||
sync: SyncStatus {
|
||||
state: if self.is_syncing { SyncState::Downloading } else { SyncState::Idle },
|
||||
best_seen_block: None,
|
||||
},
|
||||
num_peers: self.peers,
|
||||
num_active_peers: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn api<T: Into<Option<Status>>>(sync: T) -> System<Block> {
|
||||
let status = sync.into().unwrap_or_default();
|
||||
let should_have_peers = !status.is_dev;
|
||||
System::new(SystemInfo {
|
||||
impl_name: "testclient".into(),
|
||||
impl_version: "0.2.0".into(),
|
||||
chain_name: "testchain".into(),
|
||||
properties: Default::default(),
|
||||
}, Arc::new(status), should_have_peers)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_name_works() {
|
||||
assert_eq!(
|
||||
SystemApi::system_name(&()).unwrap(),
|
||||
api(None).system_name().unwrap(),
|
||||
"testclient".to_owned()
|
||||
);
|
||||
}
|
||||
@@ -43,7 +62,7 @@ fn system_name_works() {
|
||||
#[test]
|
||||
fn system_version_works() {
|
||||
assert_eq!(
|
||||
SystemApi::system_version(&()).unwrap(),
|
||||
api(None).system_version().unwrap(),
|
||||
"0.2.0".to_owned()
|
||||
);
|
||||
}
|
||||
@@ -51,7 +70,7 @@ fn system_version_works() {
|
||||
#[test]
|
||||
fn system_chain_works() {
|
||||
assert_eq!(
|
||||
SystemApi::system_chain(&()).unwrap(),
|
||||
api(None).system_chain().unwrap(),
|
||||
"testchain".to_owned()
|
||||
);
|
||||
}
|
||||
@@ -59,7 +78,54 @@ fn system_chain_works() {
|
||||
#[test]
|
||||
fn system_properties_works() {
|
||||
assert_eq!(
|
||||
SystemApi::system_properties(&()).unwrap(),
|
||||
api(None).system_properties().unwrap(),
|
||||
serde_json::map::Map::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_health() {
|
||||
assert_matches!(
|
||||
api(None).system_health().unwrap_err().kind(),
|
||||
error::ErrorKind::NotHealthy(Health {
|
||||
peers: 0,
|
||||
is_syncing: false,
|
||||
})
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
api(Status {
|
||||
peers: 5,
|
||||
is_syncing: true,
|
||||
is_dev: true,
|
||||
}).system_health().unwrap_err().kind(),
|
||||
error::ErrorKind::NotHealthy(Health {
|
||||
peers: 5,
|
||||
is_syncing: true,
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
api(Status {
|
||||
peers: 5,
|
||||
is_syncing: false,
|
||||
is_dev: false,
|
||||
}).system_health().unwrap(),
|
||||
Health {
|
||||
peers: 5,
|
||||
is_syncing: false,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
api(Status {
|
||||
peers: 0,
|
||||
is_syncing: false,
|
||||
is_dev: true,
|
||||
}).system_health().unwrap(),
|
||||
Health {
|
||||
peers: 0,
|
||||
is_syncing: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user