use crate::{proto::api, Error};
use std::time::Duration;
pub type Timestamp = time::OffsetDateTime;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum State {
PortAllocation,
Creating,
Starting,
Scheduled,
RequestReady,
Ready,
Reserved,
Allocated,
Unhealthy,
Shutdown,
Error,
}
impl std::str::FromStr for State {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"PortAllocation" => Self::PortAllocation,
"Creating" => Self::Creating,
"Starting" => Self::Starting,
"Scheduled" => Self::Scheduled,
"RequestReady" => Self::RequestReady,
"Ready" => Self::Ready,
"Reserved" => Self::Reserved,
"Allocated" => Self::Allocated,
"Unhealthy" => Self::Unhealthy,
"Shutdown" => Self::Shutdown,
"Error" => Self::Error,
unknown_state => return Err(Error::UnknownState(unknown_state.to_owned())),
})
}
}
#[derive(Clone, Debug)]
pub struct Port {
pub name: String,
pub port: u16,
}
#[derive(Clone, Debug)]
pub struct Status {
pub state: State,
pub address: std::net::IpAddr,
pub ports: Vec<Port>,
#[cfg(feature = "player-tracking")]
pub players: Option<api::game_server::status::PlayerStatus>,
}
#[derive(Clone, Debug)]
pub struct ObjectMeta {
pub name: String,
pub namespace: String,
pub uid: String,
pub resource_version: String,
pub generation: i64,
pub creation_timestamp: Timestamp,
pub deletion_timestamp: Option<Timestamp>,
pub annotations: std::collections::HashMap<String, String>,
pub labels: std::collections::HashMap<String, String>,
}
#[derive(Clone, Debug)]
pub struct HealthSpec {
pub period: Duration,
pub failure_threshold: std::num::NonZeroU32,
pub initial_delay: Duration,
}
#[derive(Clone, Debug)]
pub struct GameServer {
pub object_meta: Option<ObjectMeta>,
pub health_spec: Option<HealthSpec>,
pub status: Option<Status>,
}
impl std::convert::TryFrom<api::GameServer> for GameServer {
type Error = Error;
fn try_from(ogs: api::GameServer) -> Result<Self, Self::Error> {
let status = match ogs.status {
Some(status) => {
let address = match status.address.parse() {
Ok(addr) => addr,
Err(err) => {
return Err(Error::InvalidIp {
ip_str: status.address,
err,
});
}
};
let state = status.state.parse()?;
Some(Status {
state,
address,
ports: status
.ports
.into_iter()
.map(|port| Port {
name: port.name,
port: port.port as u16,
})
.collect(),
#[cfg(feature = "player-tracking")]
players: status.players,
})
}
None => None,
};
let object_meta = if let Some(om) = ogs.object_meta {
Some(ObjectMeta {
name: om.name,
namespace: om.namespace,
uid: om.uid,
resource_version: om.resource_version,
generation: om.generation,
creation_timestamp: Timestamp::from_unix_timestamp(om.creation_timestamp)?,
deletion_timestamp: if om.deletion_timestamp != 0 {
Some(Timestamp::from_unix_timestamp(om.deletion_timestamp)?)
} else {
None
},
annotations: om.annotations,
labels: om.labels,
})
} else {
None
};
let health_spec = ogs.spec.and_then(|spec| {
spec.health.and_then(|health| {
if health.disabled {
None
} else {
std::num::NonZeroU32::new(health.failure_threshold as u32).map(
|failure_threshold| HealthSpec {
period: Duration::from_secs(health.period_seconds as u64),
failure_threshold,
initial_delay: Duration::from_secs(health.initial_delay_seconds as u64),
},
)
}
})
});
Ok(Self {
object_meta,
health_spec,
status,
})
}
}
#[cfg(test)]
mod test {
use super::State;
#[test]
fn string_states() {
assert_eq!(
"PortAllocation".parse::<State>().unwrap(),
State::PortAllocation
);
assert_eq!("Creating".parse::<State>().unwrap(), State::Creating);
assert_eq!("Starting".parse::<State>().unwrap(), State::Starting);
assert_eq!("Scheduled".parse::<State>().unwrap(), State::Scheduled);
assert_eq!(
"RequestReady".parse::<State>().unwrap(),
State::RequestReady
);
assert_eq!("Ready".parse::<State>().unwrap(), State::Ready);
assert_eq!("Shutdown".parse::<State>().unwrap(), State::Shutdown);
assert_eq!("Error".parse::<State>().unwrap(), State::Error);
assert_eq!("Unhealthy".parse::<State>().unwrap(), State::Unhealthy);
assert_eq!("Reserved".parse::<State>().unwrap(), State::Reserved);
assert_eq!("Allocated".parse::<State>().unwrap(), State::Allocated);
}
}