g1t/services/packages/src/quota.rs

131 lines5,229 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member1//! How much a free workspace's packages may hold.
2//!
3//! Billing says, per workspace, whether it is on the plan and how much
4//! public and private package storage is free (`entitlements`). On the plan
5//! nothing is refused: storage past the free amounts is charged. Without
6//! it, a push that would take the workspace's public or private packages
7//! past their free amount is refused before anything is kept. What a
8//! workspace holds is `workspace_blobs`: each file once, so a layer it
9//! already has adds nothing.
10
11use serde::Deserialize;
12
13/// What billing allows a workspace, as far as packages go.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Allowance {
17 #[serde(default)]
18 pub has_plan: bool,
19 #[serde(default)]
20 pub package_public_free_bytes: i64,
21 #[serde(default)]
22 pub package_private_free_bytes: i64,
23}
24
25/// A push refused for storage.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct Refusal {
28 pub public: bool,
29 pub limit: u64,
30 pub used: u64,
31 pub adding: u64,
32}
33
34/// Whether `adding` more bytes may go into the workspace's public (or
35/// private) packages, which hold `used` now. A free amount of 0 is billing
36/// not saying (an older billing), and refuses nothing.
37pub fn decide(allowance: &Allowance, public: bool, used: u64, adding: u64) -> Result<(), Refusal> {
38 if allowance.has_plan || adding == 0 {
39 return Ok(());
40 }
41 let free = if public { allowance.package_public_free_bytes } else { allowance.package_private_free_bytes };
42 if free <= 0 {
43 return Ok(());
44 }
45 let limit = free as u64;
46 if used.saturating_add(adding) > limit {
47 return Err(Refusal { public, limit, used, adding });
48 }
49 Ok(())
50}
51
52/// Bytes for people: `512 KB`, `480.3 MB`, `10 GB`.
53pub fn bytes(n: u64) -> String {
54 const UNITS: [(&str, u64); 3] = [("GB", 1_000_000_000), ("MB", 1_000_000), ("KB", 1_000)];
55 for (unit, size) in UNITS {
56 if n >= size {
57 let value = n as f64 / size as f64;
58 return if (value - value.round()).abs() < 0.05 {
59 format!("{} {unit}", value.round() as u64)
60 } else {
61 format!("{value:.1} {unit}")
62 };
63 }
64 }
65 format!("{n} bytes")
66}
67
68/// What the refusal says: the limit, what is used, and what lifts it.
69pub fn message(workspace: &str, refusal: &Refusal) -> String {
70 let kind = if refusal.public { "public" } else { "private" };
71 format!(
72 "{workspace}'s {kind} packages may hold {} without the g1t plan; they hold {} and this push adds {}. \
73 The g1t plan lifts the limit: https://g1t.sh/{workspace}/-/billing",
74 bytes(refusal.limit),
75 bytes(refusal.used),
76 bytes(refusal.adding)
77 )
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 const FREE: Allowance = Allowance {
85 has_plan: false,
86 package_public_free_bytes: 10_000_000_000,
87 package_private_free_bytes: 500_000_000,
88 };
89
90 #[test]
91 fn a_free_workspace_is_refused_past_each_free_amount() {
92 assert_eq!(decide(&FREE, false, 400_000_000, 100_000_000), Ok(()), "exactly at the limit is allowed");
93 let refused = decide(&FREE, false, 400_000_000, 100_000_001).unwrap_err();
94 assert_eq!(refused, Refusal { public: false, limit: 500_000_000, used: 400_000_000, adding: 100_000_001 });
95 assert_eq!(decide(&FREE, true, 400_000_000, 100_000_001), Ok(()), "public has its own amount");
96 assert!(decide(&FREE, true, 9_999_000_000, 2_000_000).is_err());
97 }
98
99 #[test]
100 fn the_plan_and_what_adds_nothing_are_never_refused() {
101 let plan = Allowance { has_plan: true, ..FREE };
102 assert_eq!(decide(&plan, false, u64::MAX / 2, u64::MAX / 4), Ok(()));
103 assert_eq!(decide(&FREE, false, 900_000_000, 0), Ok(()), "a layer already held adds nothing");
104 assert_eq!(decide(&Allowance::default(), false, 900_000_000, 1), Ok(()), "billing not saying refuses nothing");
105 }
106
107 #[test]
108 fn the_refusal_names_the_limit_the_use_and_the_way_out() {
109 let refusal = Refusal { public: false, limit: 500_000_000, used: 480_300_000, adding: 30_000_000 };
110 assert_eq!(
111 message("acme", &refusal),
112 "acme's private packages may hold 500 MB without the g1t plan; they hold 480.3 MB and this push adds 30 MB. \
113 The g1t plan lifts the limit: https://g1t.sh/acme/-/billing"
114 );
115 assert_eq!(bytes(999), "999 bytes");
116 assert_eq!(bytes(1_500), "1.5 KB");
117 assert_eq!(bytes(10_000_000_000), "10 GB");
118 }
119
120 #[test]
121 fn billings_answer_is_read_for_the_three_fields_only() {
122 let read: Allowance = serde_json::from_value(serde_json::json!({
123 "workspace": "acme", "plan": "free", "hasPlan": false,
124 "packagePublicFreeBytes": 10, "packagePrivateFreeBytes": 5,
125 }))
126 .unwrap();
127 assert_eq!(read, Allowance { has_plan: false, package_public_free_bytes: 10, package_private_free_bytes: 5 });
128 let older: Allowance = serde_json::from_value(serde_json::json!({ "workspace": "acme" })).unwrap();
129 assert_eq!(older, Allowance::default());
130 }
131}