use crate::ic::IcDashboardReportProvenance;
use serde::{Deserialize as SerdeDeserialize, Serialize};
use std::fmt;
#[cfg(feature = "host")]
use std::path::PathBuf;
use thiserror::Error as ThisError;
#[derive(Clone, Copy, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum IcNodeStatusScope {
DashboardMainnetDefault,
}
impl IcNodeStatusScope {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::DashboardMainnetDefault => super::IC_NODE_STATUS_SCOPE,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum IcNodeOperationalStatus {
Up,
Down,
Disabled,
Degraded,
Unknown,
}
impl IcNodeOperationalStatus {
#[must_use]
pub fn from_raw(raw: &str) -> Self {
match raw {
"UP" => Self::Up,
"DOWN" => Self::Down,
"DISABLED" => Self::Disabled,
"DEGRADED" => Self::Degraded,
_ => Self::Unknown,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Up => "up",
Self::Down => "down",
Self::Disabled => "disabled",
Self::Degraded => "degraded",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for IcNodeOperationalStatus {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, SerdeDeserialize, Serialize)]
pub struct IcNodeStatusCounts {
pub total: usize,
pub up: usize,
pub down: usize,
pub disabled: usize,
pub degraded: usize,
pub unknown: usize,
}
impl IcNodeStatusCounts {
#[must_use]
pub const fn non_up(&self) -> usize {
self.total.saturating_sub(self.up)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, SerdeDeserialize, Serialize)]
pub struct IcNodeAssignmentStatusCounts {
pub assigned: IcNodeStatusCounts,
pub unassigned: IcNodeStatusCounts,
pub api_boundary: IcNodeStatusCounts,
pub unknown: IcNodeStatusCounts,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusGroupCounts {
pub statuses: IcNodeStatusCounts,
pub assignment_statuses: IcNodeAssignmentStatusCounts,
}
#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
pub struct IcNodeStatusRow {
pub node_id: String,
pub node_operator_id: String,
pub node_provider_id: String,
pub node_provider_name: String,
pub node_type: String,
pub node_reward_type: String,
pub status: String,
pub alert_name: Option<String>,
pub subnet_id: Option<String>,
pub cloud_engine_subnet_id: Option<String>,
pub data_center_id: String,
pub data_center_name: String,
pub owner: String,
pub region: String,
pub guestos_version: Option<String>,
pub guestos_tee_active: Option<bool>,
pub ip_address: Option<String>,
pub ipv4_connectivity_status: Option<bool>,
pub node_hardware_generation: Option<String>,
}
impl IcNodeStatusRow {
#[must_use]
pub fn operational_status(&self) -> IcNodeOperationalStatus {
IcNodeOperationalStatus::from_raw(&self.status)
}
#[must_use]
pub fn is_non_up(&self) -> bool {
self.operational_status() != IcNodeOperationalStatus::Up
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusCacheEvidence {
pub cache_path: String,
pub cache_fresh: bool,
pub age_seconds: u64,
pub stale_after_seconds: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusObservation {
#[serde(flatten)]
pub source: IcDashboardReportProvenance,
pub scope: IcNodeStatusScope,
pub cloud_engine_nodes_included: bool,
pub cache: Option<IcNodeStatusCacheEvidence>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusSnapshot {
#[serde(flatten)]
pub observation: IcNodeStatusObservation,
pub node_count: usize,
pub counts: IcNodeStatusGroupCounts,
pub nodes: Vec<IcNodeStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcNodeStatusSnapshotRequest {
pub source_endpoint: String,
pub now_unix_secs: u64,
}
impl IcNodeStatusSnapshotRequest {
#[must_use]
pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
Self {
source_endpoint: source_endpoint.into(),
now_unix_secs,
}
}
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcNodeStatusSourceData {
pub source: crate::ic::IcSourceRequest,
pub scope: IcNodeStatusScope,
pub cloud_engine_nodes_included: bool,
pub nodes: Vec<IcNodeStatusRow>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct IcNodeStatusView {
pub target: Option<String>,
pub include_all: bool,
}
impl IcNodeStatusView {
#[must_use]
pub const fn attention() -> Self {
Self {
target: None,
include_all: false,
}
}
#[must_use]
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
#[must_use]
pub const fn with_all(mut self, include_all: bool) -> Self {
self.include_all = include_all;
self
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusReport {
#[serde(flatten)]
pub observation: IcNodeStatusObservation,
pub snapshot_node_count: usize,
pub counts: IcNodeStatusGroupCounts,
pub include_all: bool,
pub requested_target: Option<String>,
pub resolved_target: Option<String>,
pub resolved_from: Option<String>,
pub returned_node_count: usize,
pub nodes: Vec<IcNodeStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcSubnetStatusRow {
pub subnet_id: String,
pub statuses: IcNodeStatusCounts,
pub fault_tolerance_node_count: usize,
pub additional_down_nodes_to_exceed_fault_tolerance: usize,
pub additional_non_up_nodes_to_exceed_fault_tolerance: usize,
pub down_fault_tolerance_exceeded: bool,
pub conservative_non_up_fault_tolerance_exceeded: bool,
pub non_up_nodes: Vec<IcNodeStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcSubnetStatusReport {
#[serde(flatten)]
pub observation: IcNodeStatusObservation,
pub snapshot_node_count: usize,
pub assigned_node_count: usize,
pub subnet_count: usize,
pub attention_subnet_count: usize,
pub include_all: bool,
pub requested_target: Option<String>,
pub resolved_target: Option<String>,
pub resolved_from: Option<String>,
pub returned_subnet_count: usize,
pub subnets: Vec<IcSubnetStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeProviderStatusRow {
pub node_provider_id: String,
pub node_provider_name: String,
pub counts: IcNodeStatusGroupCounts,
pub non_up_nodes: Vec<IcNodeStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeProviderStatusReport {
#[serde(flatten)]
pub observation: IcNodeStatusObservation,
pub snapshot_node_count: usize,
pub provider_count: usize,
pub attention_provider_count: usize,
pub include_all: bool,
pub requested_target: Option<String>,
pub resolved_target: Option<String>,
pub resolved_from: Option<String>,
pub returned_provider_count: usize,
pub providers: Vec<IcNodeProviderStatusRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
pub enum IcNodeStatusProjectionError {
#[error("invalid observed node-status snapshot: {reason}")]
InvalidSnapshot {
reason: String,
},
#[error("{kind} target must not be empty")]
EmptyTarget {
kind: &'static str,
},
#[error("{kind} target {target:?} did not match the observed snapshot")]
UnknownTarget {
kind: &'static str,
target: String,
},
#[error("{kind} prefix {prefix:?} is ambiguous; matches: {matches:?}")]
AmbiguousTarget {
kind: &'static str,
prefix: String,
matches: Vec<String>,
},
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcNodeStatusCacheRequest {
pub cache_root: PathBuf,
pub network: String,
}
#[cfg(feature = "host")]
impl IcNodeStatusCacheRequest {
#[must_use]
pub fn new(cache_root: impl Into<PathBuf>, network: impl Into<String>) -> Self {
Self {
cache_root: cache_root.into(),
network: network.into(),
}
}
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcNodeStatusRefreshRequest {
pub cache: IcNodeStatusCacheRequest,
pub source_endpoint: String,
pub now_unix_secs: u64,
pub lock_stale_after_seconds: u64,
}
#[cfg(feature = "host")]
impl IcNodeStatusRefreshRequest {
#[must_use]
pub fn new(
cache_root: impl Into<PathBuf>,
network: impl Into<String>,
source_endpoint: impl Into<String>,
now_unix_secs: u64,
lock_stale_after_seconds: u64,
) -> Self {
Self {
cache: IcNodeStatusCacheRequest::new(cache_root, network),
source_endpoint: source_endpoint.into(),
now_unix_secs,
lock_stale_after_seconds,
}
}
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcNodeStatusReadRequest {
pub refresh: IcNodeStatusRefreshRequest,
pub view: IcNodeStatusView,
pub force_refresh: bool,
}
#[cfg(feature = "host")]
impl IcNodeStatusReadRequest {
#[must_use]
pub fn new(
cache_root: impl Into<PathBuf>,
network: impl Into<String>,
source_endpoint: impl Into<String>,
now_unix_secs: u64,
) -> Self {
Self {
refresh: IcNodeStatusRefreshRequest::new(
cache_root,
network,
source_endpoint,
now_unix_secs,
super::DEFAULT_IC_NODE_STATUS_REFRESH_LOCK_STALE_SECONDS,
),
view: IcNodeStatusView::attention(),
force_refresh: false,
}
}
#[must_use]
pub fn with_view(mut self, view: IcNodeStatusView) -> Self {
self.view = view;
self
}
#[must_use]
pub const fn with_force_refresh(mut self, force_refresh: bool) -> Self {
self.force_refresh = force_refresh;
self
}
}
#[cfg(feature = "host")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcNodeStatusRefreshReport {
pub schema_version: u32,
pub network: String,
pub source_endpoint: String,
pub fetched_at: String,
pub fetched_by: String,
pub cache_path: String,
pub refresh_lock_path: String,
pub replaced_existing_cache: bool,
pub node_count: usize,
pub counts: IcNodeStatusGroupCounts,
}
#[cfg(feature = "host")]
#[derive(Debug, ThisError)]
pub enum IcNodeStatusHostError {
#[error("observed IC node status supports only the mainnet `ic` network, not {network:?}")]
UnsupportedNetwork {
network: String,
},
#[error(transparent)]
Source(#[from] crate::ic::IcHostError),
#[error(transparent)]
Projection(#[from] IcNodeStatusProjectionError),
#[error("observed node-status cache is missing at {}", path.display())]
MissingCache {
path: PathBuf,
},
#[error("failed to read observed node-status cache at {}: {source}", path.display())]
ReadCache {
path: PathBuf,
source: std::io::Error,
},
#[error("failed to parse observed node-status cache at {}: {source}", path.display())]
ParseCache {
path: PathBuf,
source: serde_json::Error,
},
#[error("observed node-status cache schema {version} is unsupported; expected {expected}")]
UnsupportedCacheSchemaVersion {
version: u32,
expected: u32,
},
#[error("observed node-status cache network is {actual:?}, expected {requested:?}")]
CacheNetworkMismatch {
requested: String,
actual: String,
},
#[error(
"observed node-status cache identity mismatch at {}: {field} is {actual:?}, expected {expected:?}",
path.display()
)]
CacheIdentityMismatch {
path: PathBuf,
field: &'static str,
expected: String,
actual: String,
},
#[error("invalid observed node-status cache at {}: {reason}", path.display())]
InvalidCache {
path: PathBuf,
reason: String,
},
#[error("failed to serialize observed node-status cache at {}: {source}", path.display())]
SerializeCache {
path: PathBuf,
source: serde_json::Error,
},
#[error(transparent)]
Cache(#[from] crate::cache_file::HostCacheError),
}