Skip to content

Commit 5e604b4

Browse files
authored
feat: build queue (#532)
1 parent d60f642 commit 5e604b4

File tree

15 files changed

+546
-150
lines changed

15 files changed

+546
-150
lines changed

Cargo.lock

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

admin/src/args.rs

+14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ pub enum Command {
2424

2525
/// Manage project names
2626
ProjectNames,
27+
28+
/// Viewing and managing stats
29+
#[command(subcommand)]
30+
Stats(StatsCommand),
2731
}
2832

2933
#[derive(Subcommand, Debug)]
@@ -55,3 +59,13 @@ pub enum AcmeCommand {
5559
credentials: PathBuf,
5660
},
5761
}
62+
63+
#[derive(Subcommand, Debug)]
64+
pub enum StatsCommand {
65+
/// View load stats
66+
Load {
67+
/// Clear the loads counter
68+
#[arg(long)]
69+
clear: bool,
70+
},
71+
}

admin/src/client.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::{Context, Result};
22
use serde::{de::DeserializeOwned, Serialize};
33
use shuttle_common::{
4-
models::{project, ToJson},
4+
models::{project, stats, ToJson},
55
project::ProjectName,
66
};
77
use tracing::trace;
@@ -43,6 +43,15 @@ impl Client {
4343
self.get("/admin/projects").await
4444
}
4545

46+
pub async fn get_load(&self) -> Result<stats::LoadResponse> {
47+
self.get("/admin/stats/load").await
48+
}
49+
50+
pub async fn clear_load(&self) -> Result<stats::LoadResponse> {
51+
self.delete("/admin/stats/load", Option::<String>::None)
52+
.await
53+
}
54+
4655
async fn post<T: Serialize, R: DeserializeOwned>(
4756
&self,
4857
path: &str,
@@ -67,6 +76,30 @@ impl Client {
6776
.context("failed to extract json body from post response")
6877
}
6978

79+
async fn delete<T: Serialize, R: DeserializeOwned>(
80+
&self,
81+
path: &str,
82+
body: Option<T>,
83+
) -> Result<R> {
84+
trace!(self.api_key, "using api key");
85+
86+
let mut builder = reqwest::Client::new()
87+
.delete(format!("{}{}", self.api_url, path))
88+
.bearer_auth(&self.api_key);
89+
90+
if let Some(body) = body {
91+
builder = builder.json(&body);
92+
}
93+
94+
builder
95+
.send()
96+
.await
97+
.context("failed to make delete request")?
98+
.to_json()
99+
.await
100+
.context("failed to extract json body from delete response")
101+
}
102+
70103
async fn get<R: DeserializeOwned>(&self, path: &str) -> Result<R> {
71104
reqwest::Client::new()
72105
.get(format!("{}{}", self.api_url, path))

admin/src/main.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clap::Parser;
22
use shuttle_admin::{
3-
args::{AcmeCommand, Args, Command},
3+
args::{AcmeCommand, Args, Command, StatsCommand},
44
client::Client,
55
config::get_api_key,
66
};
@@ -141,6 +141,20 @@ async fn main() {
141141

142142
res
143143
}
144+
Command::Stats(StatsCommand::Load { clear }) => {
145+
let resp = if clear {
146+
client.clear_load().await.expect("to delete load stats")
147+
} else {
148+
client.get_load().await.expect("to get load stats")
149+
};
150+
151+
let has_capacity = if resp.has_capacity { "a" } else { "no" };
152+
153+
format!(
154+
"Currently {} builds are running and there is {} capacity for new builds",
155+
resp.builds_count, has_capacity
156+
)
157+
}
144158
};
145159

146160
println!("{res}");

common/src/models/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod project;
44
pub mod resource;
55
pub mod secret;
66
pub mod service;
7+
pub mod stats;
78
pub mod user;
89

910
use anyhow::{Context, Result};

common/src/models/stats.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use serde::{Deserialize, Serialize};
2+
use uuid::Uuid;
3+
4+
#[derive(Deserialize, Serialize)]
5+
pub struct LoadRequest {
6+
pub id: Uuid,
7+
}
8+
9+
#[derive(Deserialize, Serialize)]
10+
pub struct LoadResponse {
11+
pub builds_count: usize,
12+
pub has_capacity: bool,
13+
}

deployer/src/args.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{net::SocketAddr, path::PathBuf};
22

33
use clap::Parser;
44
use fqdn::FQDN;
5+
use hyper::Uri;
56
use shuttle_common::{project::ProjectName, Port};
67

78
/// Program to handle the deploys for a single project
@@ -33,6 +34,10 @@ pub struct Args {
3334
#[clap(long, default_value = "0.0.0.0:8000")]
3435
pub proxy_address: SocketAddr,
3536

37+
/// Address to reach gateway's control plane at
38+
#[clap(long, default_value = "http://gateway:8001")]
39+
pub gateway_uri: Uri,
40+
3641
/// Project being served by this deployer
3742
#[clap(long)]
3843
pub project: ProjectName,

deployer/src/deployment/deploy_layer.rs

+41-51
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,9 @@ mod tests {
362362

363363
use crate::{
364364
deployment::{
365-
deploy_layer::LogType, provisioner_factory, runtime_logger,
366-
storage_manager::StorageManager, ActiveDeploymentsGetter, Built, DeploymentManager,
367-
Queued,
365+
deploy_layer::LogType, gateway_client::BuildQueueClient, provisioner_factory,
366+
runtime_logger, storage_manager::StorageManager, ActiveDeploymentsGetter, Built,
367+
DeploymentManager, Queued,
368368
},
369369
persistence::{SecretRecorder, State},
370370
};
@@ -529,16 +529,29 @@ mod tests {
529529
}
530530
}
531531

532+
#[derive(Clone)]
533+
struct StubBuildQueueClient;
534+
535+
#[async_trait::async_trait]
536+
impl BuildQueueClient for StubBuildQueueClient {
537+
async fn get_slot(
538+
&self,
539+
_id: Uuid,
540+
) -> Result<bool, crate::deployment::gateway_client::Error> {
541+
Ok(true)
542+
}
543+
544+
async fn release_slot(
545+
&self,
546+
_id: Uuid,
547+
) -> Result<(), crate::deployment::gateway_client::Error> {
548+
Ok(())
549+
}
550+
}
551+
532552
#[tokio::test(flavor = "multi_thread")]
533553
async fn deployment_to_be_queued() {
534-
let deployment_manager = DeploymentManager::new(
535-
StubAbstractProvisionerFactory,
536-
StubRuntimeLoggerFactory,
537-
RECORDER.clone(),
538-
RECORDER.clone(),
539-
StubActiveDeploymentGetter,
540-
PathBuf::from("/tmp"),
541-
);
554+
let deployment_manager = get_deployment_manager();
542555

543556
let queued = get_queue("sleep-async");
544557
let id = queued.id;
@@ -650,14 +663,7 @@ mod tests {
650663

651664
#[tokio::test(flavor = "multi_thread")]
652665
async fn deployment_self_stop() {
653-
let deployment_manager = DeploymentManager::new(
654-
StubAbstractProvisionerFactory,
655-
StubRuntimeLoggerFactory,
656-
RECORDER.clone(),
657-
RECORDER.clone(),
658-
StubActiveDeploymentGetter,
659-
PathBuf::from("/tmp"),
660-
);
666+
let deployment_manager = get_deployment_manager();
661667

662668
let queued = get_queue("self-stop");
663669
let id = queued.id;
@@ -730,14 +736,7 @@ mod tests {
730736

731737
#[tokio::test(flavor = "multi_thread")]
732738
async fn deployment_bind_panic() {
733-
let deployment_manager = DeploymentManager::new(
734-
StubAbstractProvisionerFactory,
735-
StubRuntimeLoggerFactory,
736-
RECORDER.clone(),
737-
RECORDER.clone(),
738-
StubActiveDeploymentGetter,
739-
PathBuf::from("/tmp"),
740-
);
739+
let deployment_manager = get_deployment_manager();
741740

742741
let queued = get_queue("bind-panic");
743742
let id = queued.id;
@@ -810,14 +809,7 @@ mod tests {
810809

811810
#[tokio::test(flavor = "multi_thread")]
812811
async fn deployment_main_panic() {
813-
let deployment_manager = DeploymentManager::new(
814-
StubAbstractProvisionerFactory,
815-
StubRuntimeLoggerFactory,
816-
RECORDER.clone(),
817-
RECORDER.clone(),
818-
StubActiveDeploymentGetter,
819-
PathBuf::from("/tmp"),
820-
);
812+
let deployment_manager = get_deployment_manager();
821813

822814
let queued = get_queue("main-panic");
823815
let id = queued.id;
@@ -885,14 +877,7 @@ mod tests {
885877

886878
#[tokio::test]
887879
async fn deployment_from_run() {
888-
let deployment_manager = DeploymentManager::new(
889-
StubAbstractProvisionerFactory,
890-
StubRuntimeLoggerFactory,
891-
RECORDER.clone(),
892-
RECORDER.clone(),
893-
StubActiveDeploymentGetter,
894-
PathBuf::from("/tmp"),
895-
);
880+
let deployment_manager = get_deployment_manager();
896881

897882
let id = Uuid::new_v4();
898883
deployment_manager
@@ -940,14 +925,7 @@ mod tests {
940925

941926
#[tokio::test]
942927
async fn scope_with_nil_id() {
943-
let deployment_manager = DeploymentManager::new(
944-
StubAbstractProvisionerFactory,
945-
StubRuntimeLoggerFactory,
946-
RECORDER.clone(),
947-
RECORDER.clone(),
948-
StubActiveDeploymentGetter,
949-
PathBuf::from("/tmp"),
950-
);
928+
let deployment_manager = get_deployment_manager();
951929

952930
let id = Uuid::nil();
953931
deployment_manager
@@ -973,6 +951,18 @@ mod tests {
973951
);
974952
}
975953

954+
fn get_deployment_manager() -> DeploymentManager {
955+
DeploymentManager::builder()
956+
.abstract_factory(StubAbstractProvisionerFactory)
957+
.runtime_logger_factory(StubRuntimeLoggerFactory)
958+
.build_log_recorder(RECORDER.clone())
959+
.secret_recorder(RECORDER.clone())
960+
.active_deployment_getter(StubActiveDeploymentGetter)
961+
.artifacts_path(PathBuf::from("/tmp"))
962+
.queue_client(StubBuildQueueClient)
963+
.build()
964+
}
965+
976966
fn get_queue(name: &str) -> Queued {
977967
let enc = GzEncoder::new(Vec::new(), Compression::fast());
978968
let mut tar = tar::Builder::new(enc);

0 commit comments

Comments
 (0)