Skip to content

Commit

Permalink
feat: better quota error
Browse files Browse the repository at this point in the history
  • Loading branch information
pxseu committed May 29, 2023
1 parent f607f75 commit 34f3699
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/commands/ignite/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ pub async fn update_deployment_config(
let (skus, quota) = tokio::join!(get_skus(http), get_quotas(http, &project.id));
let (skus, quota) = (skus?, quota?);

quota.can_deploy(&config.resources, &config.volume)?;
quota.can_deploy(&config.resources, &config.volume, project)?;

let (resources, volume_size) = if !project.is_personal() {
(config.resources, config.volume.map(|v| v.size))
Expand Down
32 changes: 24 additions & 8 deletions src/commands/projects/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};

use crate::{
commands::ignite::types::{Resources, Volume},
utils::size::{parse_size, unit_multiplier},
utils::size::{parse_size, unit_multiplier, user_friendly_size},
};

// types for the API response
Expand All @@ -26,12 +26,17 @@ pub struct Project {
pub namespace: String,
#[serde(rename = "type")]
pub type_: String,
pub tier: String,
}

impl Project {
pub fn is_personal(&self) -> bool {
self.type_ == "personal"
}

pub fn is_paid(&self) -> bool {
self.tier == "paid"
}
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -114,23 +119,34 @@ impl Quotas {
}
}

pub fn can_deploy(&self, resources: &Resources, volume: &Option<Volume>) -> Result<()> {
pub fn can_deploy(
&self,
resources: &Resources,
volume: &Option<Volume>,
project: &Project,
) -> Result<()> {
let total = self.total_quota();
let usage = self.usage_quota();

let error = if project.is_paid() {
"Please contact us to increase your quota."
} else {
"Please attach a payment method to your project."
};

if usage.vcpu + resources.vcpu > total.vcpu {
bail!(
"Not enough vCPU quota, you need additional {} vCPU. Please contact support.",
usage.vcpu + resources.vcpu - total.vcpu
"Not enough vCPU quota, you need additional {} vCPU. {error}",
usage.vcpu + resources.vcpu - total.vcpu,
);
}

let ram = parse_size(&resources.ram)?;

if usage.ram + ram > total.ram {
bail!(
"Not enough RAM quota, you need additional {}B RAM. Please contact support.",
usage.ram + ram - total.ram
"Not enough Memory quota, you need additional {}'s of RAM. {error}",
user_friendly_size(usage.ram + ram - total.ram)?
);
}

Expand All @@ -139,8 +155,8 @@ impl Quotas {

if usage.volume + volume > total.volume {
bail!(
"Not enough volume quota, you need additional {}B volume. Please contact support.",
usage.volume + volume - total.volume
"Not enough Volume quota, you need additional {}'s of storage. {error}",
user_friendly_size(usage.volume + volume - total.volume)?
);
}
}
Expand Down
36 changes: 34 additions & 2 deletions src/utils/size.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use anyhow::{anyhow, Result};

// bytes have to be last because all other end with it
pub const BYTE_UNITS: [&str; 4] = ["GB", "MB", "KB", "B"];

pub mod unit_multiplier {
use anyhow::{bail, Result};

// order matters here
pub const BYTE_UNITS: [&str; 4] = ["GB", "MB", "KB", "B"];
pub const B: u64 = 1;
pub const KB: u64 = 1024;
pub const MB: u64 = 1024 * 1024;
Expand All @@ -25,11 +26,12 @@ pub mod unit_multiplier {
pub fn parse_size(size: &str) -> Result<u64> {
let mut size = size.trim().to_uppercase();

// so stuff doesn't break
if size.ends_with(['G', 'M', 'K']) {
size = format!("{size}B");
}

let Some(unit) = BYTE_UNITS.iter().find(|unit| size.ends_with(&unit.to_string())) else {
let Some(unit) = unit_multiplier::BYTE_UNITS.iter().find(|unit| size.ends_with(&unit.to_string())) else {
return Err(anyhow!("Invalid size unit: {size}"));
};

Expand All @@ -40,6 +42,20 @@ pub fn parse_size(size: &str) -> Result<u64> {
Ok(size * unit_multiplier::from_str(unit)?)
}

pub fn user_friendly_size(size: u64) -> Result<String> {
for unit in unit_multiplier::BYTE_UNITS {
let factor = unit_multiplier::from_str(unit)?;

if size < factor {
continue;
}

return Ok(format!("{}{unit}", size / factor));
}

Ok(String::from("0B"))
}

// pub fn is_valid_mem_size(n: u64, min: u64, max: u64) -> bool {
// n >= min && n <= max && n.is_power_of_two()
// }
Expand All @@ -50,6 +66,7 @@ mod test {

#[test]
fn test_parse_size() {
assert_eq!(parse_size("0B").unwrap(), 0);
assert_eq!(parse_size("1B").unwrap(), 1);
assert_eq!(parse_size("1KB").unwrap(), 1024);
assert_eq!(parse_size("1MB").unwrap(), 1024 * 1024);
Expand All @@ -64,4 +81,19 @@ mod test {
assert!(parse_size("1B 1").is_err());
assert!(parse_size("-1B").is_err());
}

#[test]
fn test_user_friendly_size_uncommon() {
assert_eq!(user_friendly_size(0).unwrap(), "0B");
assert_eq!(user_friendly_size(1).unwrap(), "1B");
assert_eq!(user_friendly_size(1024).unwrap(), "1KB");
assert_eq!(user_friendly_size(1024 * 1024).unwrap(), "1MB");
assert_eq!(user_friendly_size(1024 * 1024 * 1024).unwrap(), "1GB");

assert_eq!(user_friendly_size(1024 * 1024 * 512).unwrap(), "512MB");
assert_eq!(
user_friendly_size(1024 * 1024 * 1024 * 512).unwrap(),
"512GB"
);
}
}

0 comments on commit 34f3699

Please sign in to comment.