use std::borrow::Cow;
use std::collections::HashMap;
use bollard::secret::HostConfig;
use bollard::secret::TaskSpecResources;
use bon::Builder;
use crankshaft_config::backend::Defaults;
use tracing::debug;
#[derive(Builder, Clone, Debug)]
#[builder(builder_type = Builder)]
pub struct Resources {
pub(crate) cpu: Option<f64>,
pub(crate) cpu_limit: Option<f64>,
pub(crate) ram: Option<f64>,
pub(crate) ram_limit: Option<f64>,
pub(crate) disk: Option<f64>,
#[builder(into)]
pub(crate) preemptible: Option<bool>,
#[builder(into, default)]
pub(crate) zones: Vec<String>,
}
impl Resources {
pub fn cpu(&self) -> Option<f64> {
self.cpu
}
pub fn cpu_limit(&self) -> Option<f64> {
self.cpu_limit
}
pub fn ram(&self) -> Option<f64> {
self.ram
}
pub fn ram_limit(&self) -> Option<f64> {
self.ram_limit
}
pub fn disk(&self) -> Option<f64> {
self.disk
}
pub fn preemptible(&self) -> Option<bool> {
self.preemptible
}
pub fn zones(&self) -> &[String] {
&self.zones
}
pub fn apply(mut self, other: &Self) -> Self {
if let Some(cores) = other.cpu {
self.cpu = Some(cores);
}
if let Some(limit) = other.cpu_limit {
self.cpu_limit = Some(limit);
}
if let Some(ram) = other.ram {
self.ram = Some(ram);
}
if let Some(limit) = other.ram_limit {
self.ram_limit = Some(limit);
}
if let Some(disk) = other.disk {
self.disk = Some(disk);
}
if let Some(preemptible) = other.preemptible {
self.preemptible = Some(preemptible);
}
self.zones = other.zones.clone();
self
}
pub fn to_hashmap(&self) -> HashMap<Cow<'static, str>, Cow<'static, str>> {
let mut map = HashMap::new();
if let Some(cores) = self.cpu {
map.insert("cpu".into(), cores.to_string().into());
}
if let Some(limit) = self.cpu_limit {
map.insert("cpu_limit".into(), limit.to_string().into());
}
if let Some(ram) = self.ram {
map.insert("ram".into(), ram.to_string().into());
map.insert("ram_mb".into(), (ram * 1024.0).to_string().into());
}
if let Some(limit) = self.ram_limit {
map.insert("ram_limit".into(), limit.to_string().into());
}
if let Some(disk) = self.disk {
map.insert("disk".into(), disk.to_string().into());
map.insert("disk_mb".into(), (disk * 1024.0).to_string().into());
}
if let Some(preemptible) = self.preemptible {
map.insert("preemptible".into(), preemptible.to_string().into());
}
map
}
}
impl Default for Resources {
fn default() -> Self {
Self {
cpu: Some(1.0),
cpu_limit: None,
ram: Some(2.0),
ram_limit: None,
disk: Some(8.0),
preemptible: Some(false),
zones: Default::default(),
}
}
}
impl From<&Defaults> for Resources {
fn from(defaults: &Defaults) -> Self {
Self {
cpu: defaults.cpu(),
cpu_limit: defaults.cpu(),
ram: defaults.ram(),
ram_limit: defaults.ram_limit(),
disk: defaults.disk(),
preemptible: Default::default(),
zones: Default::default(),
}
}
}
impl From<&Resources> for HostConfig {
fn from(resources: &Resources) -> Self {
let mut host_config = Self::default();
if resources.cpu().is_some() {
debug!(
"ignoring minimum CPU reservation for a Docker daemon not participating in a swarm"
);
}
if let Some(cpu) = resources.cpu_limit() {
host_config.nano_cpus = Some((cpu * 1_000_000_000.0) as i64);
}
if resources.ram().is_some() {
debug!(
"ignoring minimum memory reservation for a Docker daemon not participating in a \
swarm"
);
}
if let Some(ram) = resources.ram_limit() {
host_config.memory = Some((ram * 1024. * 1024. * 1024.) as i64);
}
if let Some(disk) = resources.disk() {
let mut storage_opt: HashMap<String, String> = HashMap::new();
storage_opt.insert("size".to_string(), disk.to_string());
host_config.storage_opt = Some(storage_opt);
}
host_config
}
}
impl From<&Resources> for TaskSpecResources {
fn from(resources: &Resources) -> Self {
let mut spec = Self::default();
if let Some(cpu) = resources.cpu() {
spec.reservations.get_or_insert_default().nano_cpus =
Some((cpu * 1_000_000_000.0) as i64);
}
if let Some(cpu) = resources.cpu_limit() {
spec.limits.get_or_insert_default().nano_cpus = Some((cpu * 1_000_000_000.0) as i64);
}
if let Some(ram) = resources.ram() {
spec.reservations.get_or_insert_default().memory_bytes =
Some((ram * 1024. * 1024. * 1024.) as i64);
}
if let Some(ram) = resources.ram_limit() {
spec.limits.get_or_insert_default().memory_bytes =
Some((ram * 1024. * 1024. * 1024.) as i64);
}
spec
}
}
impl From<Resources> for tes::v1::types::task::Resources {
fn from(resources: Resources) -> Self {
if !resources.zones.is_empty() {
todo!("zones within resources are not yet implemented in Crankshaft");
}
Self {
cpu_cores: resources.cpu().map(|inner| inner as i32),
ram_gb: resources.ram(),
disk_gb: resources.disk(),
preemptible: resources.preemptible(),
zones: None,
backend_parameters: None,
backend_parameters_strict: None,
}
}
}