Skip to content

Commit 30a07c3

Browse files
committedNov 4, 2024·
clean up inactive workspace
1 parent 0e982de commit 30a07c3

File tree

7 files changed

+180
-89
lines changed

7 files changed

+180
-89
lines changed
 

‎lapdev-api/src/workspace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub async fn delete_workspace(
134134

135135
state
136136
.conductor
137-
.delete_workspace(ws, info.ip, info.user_agent)
137+
.delete_workspace(&ws, info.ip, info.user_agent)
138138
.await?;
139139
Ok(StatusCode::NO_CONTENT.into_response())
140140
}

‎lapdev-common/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,13 @@ pub struct WorkspaceService {
355355
}
356356

357357
#[derive(Serialize, Deserialize, Debug, Clone)]
358-
pub struct RunningWorkspace {
358+
pub struct HostWorkspace {
359359
pub id: Uuid,
360360
pub ssh_port: Option<i32>,
361361
pub ide_port: Option<i32>,
362362
pub last_inactivity: Option<DateTime<FixedOffset>>,
363+
pub created_at: DateTime<FixedOffset>,
364+
pub updated_at: Option<DateTime<FixedOffset>>,
363365
}
364366

365367
#[derive(Serialize, Deserialize, Debug, Clone)]

‎lapdev-conductor/src/rpc.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use lapdev_common::{BuildTarget, PrebuildUpdateEvent, RunningWorkspace, WorkspaceUpdateEvent};
2+
use lapdev_common::{BuildTarget, HostWorkspace, PrebuildUpdateEvent, WorkspaceUpdateEvent};
33
use lapdev_db::entities;
44
use lapdev_rpc::{error::ApiError, ConductorService};
55
use sea_orm::{ActiveModelTrait, ActiveValue};
@@ -70,24 +70,39 @@ impl ConductorService for ConductorRpc {
7070
}
7171
}
7272

73-
async fn running_workspaces(
73+
async fn running_workspaces_on_host(
7474
self,
7575
_context: tarpc::context::Context,
76-
) -> Result<Vec<RunningWorkspace>, ApiError> {
76+
) -> Result<Vec<HostWorkspace>, ApiError> {
7777
let workspaces = self
7878
.conductor
7979
.db
8080
.get_running_workspaces_on_host(self.ws_host_id)
8181
.await?;
8282
let workspaces = workspaces
8383
.into_iter()
84-
.map(|ws| RunningWorkspace {
84+
.map(|ws| HostWorkspace {
8585
id: ws.id,
8686
ssh_port: ws.ssh_port,
8787
ide_port: ws.ide_port,
8888
last_inactivity: ws.last_inactivity,
89+
created_at: ws.created_at,
90+
updated_at: ws.updated_at,
8991
})
9092
.collect();
9193
Ok(workspaces)
9294
}
95+
96+
async fn auto_delete_inactive_workspaces(self, _context: tarpc::context::Context) {
97+
let conductor = self.conductor.clone();
98+
let host_id = self.ws_host_id;
99+
tokio::spawn(async move {
100+
if let Err(e) = conductor
101+
.auto_delete_inactive_workspaces_on_host(host_id)
102+
.await
103+
{
104+
tracing::error!("auto delete inactive workspaces on host {host_id} error: {e:?}");
105+
}
106+
});
107+
}
93108
}

‎lapdev-conductor/src/server.rs

+52-11
Original file line numberDiff line numberDiff line change
@@ -1169,9 +1169,9 @@ impl Conductor {
11691169
let ws = entities::workspace::ActiveModel {
11701170
id: ActiveValue::Set(workspace_id),
11711171
deleted_at: ActiveValue::Set(None),
1172-
updated_at: ActiveValue::Set(None),
11731172
name: ActiveValue::Set(name.clone()),
11741173
created_at: ActiveValue::Set(now.into()),
1174+
updated_at: ActiveValue::Set(Some(now.into())),
11751175
status: ActiveValue::Set(WorkspaceStatus::New.to_string()),
11761176
repo_url: ActiveValue::Set(repo.url.clone()),
11771177
repo_name: ActiveValue::Set(repo.name.clone()),
@@ -2059,15 +2059,27 @@ impl Conductor {
20592059
.do_create_workspace(&user, &ws, repo, &machine_type, ip, user_agent)
20602060
.await
20612061
{
2062-
let err = if let ApiError::InternalError(e) = e {
2063-
e
2064-
} else {
2065-
e.to_string()
2066-
};
2067-
tracing::error!("create workspace failed: {err}");
2068-
let _ = conductor
2069-
.update_workspace_status(&ws, WorkspaceStatus::Failed)
2070-
.await;
2062+
tracing::error!("create workspace failed: {e:?}");
2063+
if let Ok(ws) = conductor.db.get_workspace(ws.id).await {
2064+
if let Some(usage_id) = ws.usage_id {
2065+
let now = Utc::now();
2066+
if let Ok(txn) = conductor.db.conn.begin().await {
2067+
let _ = conductor
2068+
.enterprise
2069+
.usage
2070+
.end_usage(&txn, usage_id, now.into())
2071+
.await;
2072+
let _ = txn.commit().await;
2073+
}
2074+
}
2075+
}
2076+
let _ = entities::workspace::ActiveModel {
2077+
id: ActiveValue::Set(ws.id),
2078+
status: ActiveValue::Set(WorkspaceStatus::Failed.to_string()),
2079+
..Default::default()
2080+
}
2081+
.update(&conductor.db.conn)
2082+
.await;
20712083
}
20722084
});
20732085

@@ -2189,7 +2201,7 @@ impl Conductor {
21892201

21902202
pub async fn delete_workspace(
21912203
&self,
2192-
workspace: entities::workspace::Model,
2204+
workspace: &entities::workspace::Model,
21932205
ip: Option<String>,
21942206
user_agent: Option<String>,
21952207
) -> Result<(), ApiError> {
@@ -2235,6 +2247,7 @@ impl Conductor {
22352247
let update_ws = entities::workspace::ActiveModel {
22362248
id: ActiveValue::Set(workspace.id),
22372249
status: ActiveValue::Set(WorkspaceStatus::Deleting.to_string()),
2250+
updated_at: ActiveValue::Set(Some(now.into())),
22382251
usage_id: ActiveValue::Set(None),
22392252
..Default::default()
22402253
};
@@ -2332,6 +2345,7 @@ impl Conductor {
23322345
id: ActiveValue::Set(ws.id),
23332346
status: ActiveValue::Set(status.to_string()),
23342347
deleted_at: ActiveValue::Set(Some(now.into())),
2348+
updated_at: ActiveValue::Set(Some(now.into())),
23352349
..Default::default()
23362350
}
23372351
.update(&txn)
@@ -2375,6 +2389,7 @@ impl Conductor {
23752389
entities::workspace::ActiveModel {
23762390
id: ActiveValue::Set(ws.id),
23772391
status: ActiveValue::Set(status.to_string()),
2392+
updated_at: ActiveValue::Set(Some(now.into())),
23782393
..Default::default()
23792394
}
23802395
.update(&self.db.conn)
@@ -3330,6 +3345,32 @@ impl Conductor {
33303345
.await?;
33313346
Ok(())
33323347
}
3348+
3349+
pub async fn auto_delete_inactive_workspaces_on_host(
3350+
&self,
3351+
host_id: Uuid,
3352+
) -> Result<(), ApiError> {
3353+
let workspaces = self
3354+
.db
3355+
.get_inactive_workspaces_on_host(
3356+
host_id,
3357+
(Utc::now() - Duration::from_secs(14 * 24 * 60 * 60)).into(),
3358+
)
3359+
.await?;
3360+
for ws in workspaces {
3361+
if ws.compose_parent.is_none() {
3362+
tracing::info!(
3363+
"now delete ws {} due to inactivity, last updated at {:?}",
3364+
ws.name,
3365+
ws.updated_at
3366+
);
3367+
if let Err(e) = self.delete_workspace(&ws, None, None).await {
3368+
tracing::info!("delete inactive ws {} error: {e:?}", ws.name);
3369+
}
3370+
}
3371+
}
3372+
Ok(())
3373+
}
33333374
}
33343375

33353376
pub fn encode_pkcs8_pem(key: &KeyPair) -> Result<String> {

‎lapdev-db/src/api.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{anyhow, Result};
22
use base64::{engine::general_purpose::STANDARD, Engine};
3-
use chrono::Utc;
3+
use chrono::{DateTime, FixedOffset, Utc};
44
use lapdev_common::{
55
AuthProvider, ProviderUser, UserRole, WorkspaceStatus, LAPDEV_BASE_HOSTNAME,
66
LAPDEV_ISOLATE_CONTAINER,
@@ -221,8 +221,23 @@ impl DbApi {
221221
) -> Result<Vec<entities::workspace::Model>> {
222222
let model = workspace::Entity::find()
223223
.filter(entities::workspace::Column::HostId.eq(ws_host_id))
224+
.filter(entities::workspace::Column::DeletedAt.is_null())
224225
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Running.to_string()))
226+
.all(&self.conn)
227+
.await?;
228+
Ok(model)
229+
}
230+
231+
pub async fn get_inactive_workspaces_on_host(
232+
&self,
233+
ws_host_id: Uuid,
234+
last_updated_at: DateTime<FixedOffset>,
235+
) -> Result<Vec<entities::workspace::Model>> {
236+
let model = workspace::Entity::find()
237+
.filter(entities::workspace::Column::HostId.eq(ws_host_id))
225238
.filter(entities::workspace::Column::DeletedAt.is_null())
239+
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Stopped.to_string()))
240+
.filter(entities::workspace::Column::UpdatedAt.lt(last_updated_at))
226241
.all(&self.conn)
227242
.await?;
228243
Ok(model)
@@ -234,8 +249,8 @@ impl DbApi {
234249
) -> Result<Option<entities::workspace::Model>> {
235250
let model = workspace::Entity::find()
236251
.filter(entities::workspace::Column::OrganizationId.eq(org_id))
237-
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Running.to_string()))
238252
.filter(entities::workspace::Column::DeletedAt.is_null())
253+
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Running.to_string()))
239254
.one(&self.conn)
240255
.await?;
241256
Ok(model)

‎lapdev-rpc/src/lib.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use futures::{
1414
};
1515
use lapdev_common::{
1616
BuildTarget, ContainerInfo, CreateWorkspaceRequest, DeleteWorkspaceRequest, GitBranch,
17-
PrebuildInfo, ProjectRequest, RepoBuildInfo, RepoBuildOutput, RepoBuildResult, RepoContent,
18-
RunningWorkspace, StartWorkspaceRequest, StopWorkspaceRequest,
17+
HostWorkspace, PrebuildInfo, ProjectRequest, RepoBuildInfo, RepoBuildOutput, RepoBuildResult,
18+
RepoContent, StartWorkspaceRequest, StopWorkspaceRequest,
1919
};
2020
use serde::{Deserialize, Serialize};
2121
use tarpc::transport::channel::UnboundedChannel;
@@ -111,7 +111,9 @@ pub trait ConductorService {
111111

112112
async fn update_build_repo_stderr(target: BuildTarget, line: String);
113113

114-
async fn running_workspaces() -> Result<Vec<RunningWorkspace>, ApiError>;
114+
async fn running_workspaces_on_host() -> Result<Vec<HostWorkspace>, ApiError>;
115+
116+
async fn auto_delete_inactive_workspaces();
115117
}
116118

117119
#[derive(Debug, Serialize, Deserialize)]

0 commit comments

Comments
 (0)
Please sign in to comment.