Skip to content

Commit

Permalink
add user creation web hook
Browse files Browse the repository at this point in the history
  • Loading branch information
lyang2821 committed Oct 28, 2024
1 parent 474c033 commit afdc18b
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 38 deletions.
17 changes: 17 additions & 0 deletions lapdev-api/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use chrono::Utc;
use hyper::StatusCode;
use lapdev_common::{
console::NewSessionResponse, AuditAction, AuditResourceKind, AuthProvider, ProviderUser,
UserCreationWebhook,
};
use lapdev_db::entities;
use lapdev_rpc::error::ApiError;
Expand Down Expand Up @@ -311,6 +312,22 @@ pub(crate) async fn session_authorize(
.await?;

txn.commit().await?;

{
let state = state.clone();
let id = user.id;
tokio::spawn(async move {
let webhook = state.db.get_user_creation_webhook().await?;
reqwest::Client::builder()
.build()?
.post(webhook)
.json(&UserCreationWebhook { id })
.send()
.await?;
anyhow::Ok(())
});
}

user
}
};
Expand Down
11 changes: 11 additions & 0 deletions lapdev-api/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ pub async fn delete_workspace(
if ws.user_id != user.id {
return Err(ApiError::Unauthorized);
}

if ws.status == WorkspaceStatus::PrebuildBuilding.to_string()
|| ws.status == WorkspaceStatus::Building.to_string()
|| ws.status == WorkspaceStatus::PrebuildCopying.to_string()
|| ws.status == WorkspaceStatus::New.to_string()
{
return Err(ApiError::InvalidRequest(
"Can't delete workspace when it's building".to_string(),
));
}

state
.conductor
.delete_workspace(ws, info.ip, info.user_agent)
Expand Down
5 changes: 5 additions & 0 deletions lapdev-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -818,3 +818,8 @@ pub struct GitProvider {
pub scopes: Vec<String>,
pub all_scopes: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserCreationWebhook {
pub id: Uuid,
}
5 changes: 4 additions & 1 deletion lapdev-conductor/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ mod tests {
created_by: ActiveValue::Set(Uuid::new_v4()),
repo_url: ActiveValue::Set("".to_string()),
repo_name: ActiveValue::Set("".to_string()),
oauth_id: ActiveValue::Set(Uuid::new_v4()),
host_id: ActiveValue::Set(Uuid::new_v4()),
osuser: ActiveValue::Set("".to_string()),
organization_id: ActiveValue::Set(Uuid::new_v4()),
machine_type_id: ActiveValue::Set(machine_type.id),
..Default::default()
Expand Down Expand Up @@ -473,7 +476,7 @@ mod tests {
txn.commit().await.unwrap();

assert_eq!(workspace_host.available_dedicated_cpu, 9);
assert_eq!(workspace_host.available_shared_cpu, 12);
assert_eq!(workspace_host.available_shared_cpu, 9);
assert_eq!(workspace_host.available_memory, 48);
assert_eq!(workspace_host.available_disk, 980);
}
Expand Down
117 changes: 87 additions & 30 deletions lapdev-conductor/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,18 +502,41 @@ impl Conductor {
tokio::time::sleep(Duration::from_secs(60)).await;
} else {
for org in orgs {
let workspaces = self
.enterprise
.auto_start_stop
.organization_auto_stop_workspaces(org)
.await
.unwrap_or_default();
for workspace in workspaces {
tracing::info!(
"stop workspace {} because of auto stop timeout",
workspace.name
);
let _ = self.stop_workspace(workspace, None, None).await;
{
let workspaces = self
.enterprise
.auto_start_stop
.organization_auto_stop_workspaces(&org)
.await
.unwrap_or_default();
for workspace in workspaces {
tracing::info!(
"stop workspace {} because of auto stop timeout",
workspace.name
);
let _ = self.stop_workspace(workspace, None, None).await;
}
}

if org.running_workspace_limit > 0 {
let usage = self
.enterprise
.usage
.get_monthly_cost(org.id, None, Utc::now().into(), None)
.await
.unwrap_or(0);
if usage as i64 >= org.usage_limit {
if let Ok(workspaces) = self.db.get_org_running_workspaces(org.id).await
{
for workspace in workspaces {
tracing::info!(
"stop workspace {} because of usage limit",
workspace.name
);
let _ = self.stop_workspace(workspace, None, None).await;
}
}
}
}
}
}
Expand Down Expand Up @@ -1931,6 +1954,14 @@ impl Conductor {
)
.await;

entities::organization::ActiveModel {
id: ActiveValue::Set(ws.organization_id),
has_running_workspace: ActiveValue::Set(true),
..Default::default()
}
.update(&self.db.conn)
.await?;

Ok(())
}

Expand Down Expand Up @@ -2147,9 +2178,16 @@ impl Conductor {
user_agent.clone(),
)
.await?;
if let Some(usage_id) = workspace.usage_id {
self.enterprise
.usage
.end_usage(&txn, usage_id, now.into())
.await?;
}
let update_ws = entities::workspace::ActiveModel {
id: ActiveValue::Set(workspace.id),
status: ActiveValue::Set(WorkspaceStatus::Deleting.to_string()),
usage_id: ActiveValue::Set(None),
..Default::default()
};
let ws = update_ws.update(&txn).await?;
Expand Down Expand Up @@ -2237,21 +2275,13 @@ impl Conductor {
Ok(_) => {
let status = WorkspaceStatus::Deleted;
let txn = self.db.conn.begin().await?;
if let Some(usage_id) = ws.usage_id {
self.enterprise
.usage
.end_usage(&txn, usage_id, now.into())
.await?;
}

let host = self
.db
.get_workspace_host_with_lock(&txn, ws.host_id)
.await?;
entities::workspace::ActiveModel {
id: ActiveValue::Set(ws.id),
status: ActiveValue::Set(status.to_string()),
usage_id: ActiveValue::Set(None),
deleted_at: ActiveValue::Set(Some(now.into())),
..Default::default()
}
Expand Down Expand Up @@ -2303,6 +2333,23 @@ impl Conductor {
}
};

self.update_org_has_running_workspace(ws.organization_id)
.await?;

Ok(())
}

async fn update_org_has_running_workspace(&self, org_id: Uuid) -> Result<()> {
let ws = self.db.get_org_running_workspace(org_id).await?;
if ws.is_none() {
entities::organization::ActiveModel {
id: ActiveValue::Set(org_id),
has_running_workspace: ActiveValue::Set(false),
..Default::default()
}
.update(&self.db.conn)
.await?;
}
Ok(())
}

Expand Down Expand Up @@ -2424,10 +2471,17 @@ impl Conductor {
user_agent,
)
.await?;
if let Some(usage_id) = workspace.usage_id {
self.enterprise
.usage
.end_usage(&txn, usage_id, now.into())
.await?;
}
let update_ws = entities::workspace::ActiveModel {
id: ActiveValue::Set(workspace.id),
status: ActiveValue::Set(WorkspaceStatus::Stopping.to_string()),
updated_at: ActiveValue::Set(Some(now.into())),
usage_id: ActiveValue::Set(None),
..Default::default()
};
let ws = update_ws.update(&txn).await?;
Expand Down Expand Up @@ -2520,6 +2574,15 @@ impl Conductor {
.update(&txn)
.await?;
txn.commit().await?;

entities::organization::ActiveModel {
id: ActiveValue::Set(ws.organization_id),
has_running_workspace: ActiveValue::Set(true),
..Default::default()
}
.update(&self.db.conn)
.await?;

status
}
Err(e) => {
Expand Down Expand Up @@ -2563,23 +2626,14 @@ impl Conductor {
match result {
Ok(_) => {
let status = WorkspaceStatus::Stopped;
let txn = self.db.conn.begin().await?;
if let Some(usage_id) = ws.usage_id {
self.enterprise
.usage
.end_usage(&txn, usage_id, now.into())
.await?;
}
entities::workspace::ActiveModel {
id: ActiveValue::Set(ws.id),
status: ActiveValue::Set(status.to_string()),
updated_at: ActiveValue::Set(Some(now.into())),
usage_id: ActiveValue::Set(None),
..Default::default()
}
.update(&txn)
.update(&self.db.conn)
.await?;
txn.commit().await?;
}
Err(e) => {
let e = if let ApiError::InternalError(e) = e {
Expand All @@ -2600,6 +2654,9 @@ impl Conductor {
}
};

self.update_org_has_running_workspace(ws.organization_id)
.await?;

Ok(())
}

Expand Down
32 changes: 32 additions & 0 deletions lapdev-db/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub const LAPDEV_CLUSTER_NOT_INITIATED: &str = "lapdev-cluster-not-initiated";
const LAPDEV_API_AUTH_TOKEN_KEY: &str = "lapdev-api-auth-token-key";
const LAPDEV_DEFAULT_USAGE_LIMIT: &str = "lapdev-default-org-usage-limit";
const LAPDEV_DEFAULT_RUNNING_WORKSPACE_LIMIT: &str = "lapdev-default-org-running-workspace-limit";
const LAPDEV_USER_CREATION_WEBHOOK: &str = "lapdev-user-creation-webhook";

#[derive(Clone)]
pub struct DbApi {
Expand Down Expand Up @@ -145,6 +146,10 @@ impl DbApi {
self.get_config(LAPDEV_BASE_HOSTNAME).await
}

pub async fn get_user_creation_webhook(&self) -> Result<String> {
self.get_config(LAPDEV_USER_CREATION_WEBHOOK).await
}

pub async fn is_container_isolated(&self) -> Result<bool> {
Ok(entities::config::Entity::find()
.filter(entities::config::Column::Name.eq(LAPDEV_ISOLATE_CONTAINER))
Expand Down Expand Up @@ -223,6 +228,32 @@ impl DbApi {
Ok(model)
}

pub async fn get_org_running_workspace(
&self,
org_id: Uuid,
) -> Result<Option<entities::workspace::Model>> {
let model = workspace::Entity::find()
.filter(entities::workspace::Column::OrganizationId.eq(org_id))
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Running.to_string()))
.filter(entities::workspace::Column::DeletedAt.is_null())
.one(&self.conn)
.await?;
Ok(model)
}

pub async fn get_org_running_workspaces(
&self,
org_id: Uuid,
) -> Result<Vec<entities::workspace::Model>> {
let model = workspace::Entity::find()
.filter(entities::workspace::Column::OrganizationId.eq(org_id))
.filter(entities::workspace::Column::Status.eq(WorkspaceStatus::Running.to_string()))
.filter(entities::workspace::Column::DeletedAt.is_null())
.all(&self.conn)
.await?;
Ok(model)
}

pub async fn validate_ssh_public_key(
&self,
user_id: Uuid,
Expand Down Expand Up @@ -303,6 +334,7 @@ impl DbApi {
last_auto_stop_check: ActiveValue::Set(None),
usage_limit: ActiveValue::Set(default_usage_limit),
running_workspace_limit: ActiveValue::Set(default_running_workspace_limit),
has_running_workspace: ActiveValue::Set(false),
}
.insert(txn)
.await?;
Expand Down
1 change: 1 addition & 0 deletions lapdev-db/src/entities/organization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct Model {
pub last_auto_stop_check: Option<DateTimeWithTimeZone>,
pub usage_limit: i64,
pub running_workspace_limit: i32,
pub has_running_workspace: bool,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,22 @@ impl MigrationTrait for Migration {
.integer()
.not_null(),
)
.col(
ColumnDef::new(Organization::HasRunningWorkspace)
.boolean()
.not_null(),
)
.to_owned(),
)
.await?;

manager
.create_index(
Index::create()
.name("organization_deleted_at_last_auto_stop_check_idx")
.name("organization_deleted_at_has_running_workspace_last_auto_stop_check_idx")
.table(Organization::Table)
.col(Organization::DeletedAt)
.col(Organization::HasRunningWorkspace)
.col(Organization::LastAutoStopCheck)
.to_owned(),
)
Expand All @@ -74,4 +80,5 @@ enum Organization {
LastAutoStopCheck,
UsageLimit,
RunningWorkspaceLimit,
HasRunningWorkspace,
}
Loading

0 comments on commit afdc18b

Please sign in to comment.