omni_orchestrator/schemas/v1/db/queries/
instance.rs

1use crate::models::instance::Instance;
2use anyhow::Context;
3use sqlx::{MySql, Pool};
4
5/// List instances by `region_id` and `app_id` paginated by `page` and `per_page` using a where clause.
6pub async fn list_instances_by_region(
7    pool: &Pool<MySql>,
8    region_id: i64,
9    app_id: i64,
10    page: i64,
11    per_page: i64,
12) -> anyhow::Result<Vec<Instance>> {
13    let instances = sqlx::query_as::<_, Instance>(
14        "SELECT * FROM instances WHERE region_id = ? AND app_id = ? LIMIT ?, ?",
15    )
16    .bind(region_id)
17    .bind(app_id)
18    .bind((page - 1) * per_page)
19    .bind(per_page)
20    .fetch_all(pool)
21    .await
22    .context("Failed to fetch instances")?;
23
24    Ok(instances)
25}
26
27/// Counts the total number of instances across all applications.
28/// 
29/// This function returns the total count of instances in the database.
30/// It's useful for monitoring overall resource allocation and usage.
31pub async fn count_instances(pool: &Pool<MySql>) -> anyhow::Result<i64> {
32    let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM instances")
33        .fetch_one(pool)
34        .await
35        .context("Failed to count instances")?;
36
37    Ok(count)
38}
39
40/// Retrieves a specific instance by its unique identifier.
41///
42/// This function fetches detailed information about a single compute instance.
43/// It's typically used when specific instance details are needed, such as
44/// for status monitoring or management operations.
45///
46/// # Arguments
47///
48/// * `pool` - Database connection pool for executing the query
49/// * `id` - Unique identifier of the instance to retrieve
50///
51/// # Returns
52///
53/// * `Ok(Instance)` - Successfully retrieved instance information
54/// * `Err(anyhow::Error)` - Failed to fetch instance (including if not found)
55///
56/// # Error Handling
57///
58/// Returns an error if no instance with the given ID exists or if a database
59/// error occurs during the query execution.
60pub async fn get_instance_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<Instance> {
61    let instance = sqlx::query_as::<_, Instance>("SELECT * FROM instances WHERE id = ?")
62        .bind(id)
63        .fetch_one(pool)
64        .await
65        .context("Failed to fetch instance")?;
66
67    Ok(instance)
68}
69
70/// Creates a new compute instance for an application.
71///
72/// This function provisions a new compute instance for an application with the
73/// specified instance type. The instance is initially created with a 'provisioning'
74/// status and 'running' instance_status.
75///
76/// # Arguments
77///
78/// * `pool` - Database connection pool for executing the query
79/// * `app_id` - Identifier of the application this instance belongs to
80/// * `instance_type` - Type of instance to create (e.g., 'small', 'medium', 'large')
81///
82/// # Returns
83///
84/// * `Ok(Instance)` - Successfully created instance record
85/// * `Err(anyhow::Error)` - Failed to create instance record
86///
87/// # Transaction Handling
88///
89/// This function uses a database transaction to ensure atomicity of the operation.
90/// If any part of the operation fails, the entire operation is rolled back.
91///
92/// # State Model
93///
94/// The instance is created with:
95/// - `status`: 'provisioning' - Indicates the instance is being set up
96/// - `instance_status`: 'running' - Indicates the intended operational state
97///
98/// These states will be updated as the instance progresses through its lifecycle.
99pub async fn create_instance(
100    pool: &Pool<MySql>,
101    app_id: i64,
102    instance_type: &str,
103) -> anyhow::Result<Instance> {
104    let mut tx = pool.begin().await?;
105
106    let instance = sqlx::query_as::<_, Instance>(
107        r#"INSERT INTO instances (
108            app_id, instance_type, status, instance_status
109        ) VALUES (?, ?, 'provisioning', 'running')"#,
110    )
111    .bind(app_id)
112    .bind(instance_type)
113    .fetch_one(&mut *tx)
114    .await
115    .context("Failed to create instance")?;
116
117    tx.commit().await?;
118    Ok(instance)
119}
120
121/// Updates the status and details of an existing instance.
122///
123/// This function modifies an instance record to reflect its current state and
124/// associated runtime information. It's typically called during the instance
125/// lifecycle as it changes state or is assigned to specific infrastructure.
126///
127/// # Arguments
128///
129/// * `pool` - Database connection pool for executing the query
130/// * `id` - Unique identifier of the instance to update
131/// * `status` - New provisioning status (e.g., 'provisioning', 'running', 'failed')
132/// * `instance_status` - New operational status (e.g., 'running', 'stopped', 'terminated')
133/// * `container_id` - Optional identifier of the container running the instance
134/// * `node_name` - Optional name of the node hosting the instance
135///
136/// # Returns
137///
138/// * `Ok(Instance)` - Successfully updated instance record
139/// * `Err(anyhow::Error)` - Failed to update instance
140///
141/// # Status Model
142///
143/// The instance has two status fields:
144/// - `status`: Represents the provisioning lifecycle (provisioning, running, failed)
145/// - `instance_status`: Represents the operational state (running, stopped, terminated)
146///
147/// # Transaction Handling
148///
149/// This function uses a database transaction to ensure atomicity of the operation.
150/// If any part of the operation fails, the entire operation is rolled back.
151pub async fn update_instance_status(
152    pool: &Pool<MySql>,
153    id: i64,
154    status: &str,
155    instance_status: &str,
156    container_id: Option<&str>,
157    node_name: Option<&str>,
158) -> anyhow::Result<Instance> {
159    let mut tx = pool.begin().await?;
160
161    let instance = sqlx::query_as::<_, Instance>(
162        r#"UPDATE instances 
163        SET status = ?, instance_status = ?, container_id = ?, node_name = ?, 
164            updated_at = CURRENT_TIMESTAMP 
165        WHERE id = ?"#,
166    )
167    .bind(status)
168    .bind(instance_status)
169    .bind(container_id)
170    .bind(node_name)
171    .bind(id)
172    .fetch_one(&mut *tx)
173    .await
174    .context("Failed to update instance status")?;
175
176    tx.commit().await?;
177    Ok(instance)
178}
179
180/// Deletes a specific instance from the database.
181///
182/// This function permanently removes an instance record from the database.
183/// It's typically used for cleanup operations after an instance has been 
184/// terminated, or to remove invalid instances.
185///
186/// # Arguments
187///
188/// * `pool` - Database connection pool for executing the query
189/// * `id` - Unique identifier of the instance to delete
190///
191/// # Returns
192///
193/// * `Ok(())` - Successfully deleted the instance
194/// * `Err(anyhow::Error)` - Failed to delete the instance
195///
196/// # Warning
197///
198/// This operation is irreversible and should generally only be performed after
199/// ensuring that the actual compute resource has been properly terminated.
200/// Otherwise, resource leaks may occur.
201///
202/// # Transaction Handling
203///
204/// This function uses a database transaction to ensure atomicity of the operation.
205/// If any part of the operation fails, the entire operation is rolled back.
206pub async fn delete_instance(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
207    let mut tx = pool.begin().await?;
208
209    sqlx::query("DELETE FROM instances WHERE id = ?")
210        .bind(id)
211        .execute(&mut *tx)
212        .await
213        .context("Failed to delete instance")?;
214
215    tx.commit().await?;
216    Ok(())
217}
218
219/// Retrieves all running instances for a specific application.
220///
221/// This function fetches all instances in the 'running' state for an application,
222/// ordered by creation time with the most recent first. It's useful for monitoring
223/// active compute resources and managing application scaling.
224///
225/// # Arguments
226///
227/// * `pool` - Database connection pool for executing the query
228/// * `app_id` - Unique identifier of the application whose running instances to retrieve
229///
230/// # Returns
231///
232/// * `Ok(Vec<Instance>)` - Successfully retrieved list of running instances
233/// * `Err(anyhow::Error)` - Failed to fetch running instances
234///
235/// # Use Cases
236///
237/// Common use cases include:
238/// - Monitoring active compute resources
239/// - Load balancing traffic across running instances
240/// - Determining if auto-scaling is necessary
241/// - Checking application health through instance distribution
242pub async fn get_running_instances(
243    pool: &Pool<MySql>,
244    app_id: i64,
245) -> anyhow::Result<Vec<Instance>> {
246    let instances = sqlx::query_as::<_, Instance>(
247        r#"SELECT * FROM instances 
248        WHERE app_id = ? AND instance_status = 'running'
249        ORDER BY created_at DESC"#,
250    )
251    .bind(app_id)
252    .fetch_all(pool)
253    .await
254    .context("Failed to fetch running instances")?;
255
256    Ok(instances)
257}
258
259/// Counts the number of running instances for a specific application.
260///
261/// This function returns the total count of instances in the 'running' state
262/// for an application. It's more efficient than fetching all instances and 
263/// counting them, especially when only the count is needed.
264///
265/// # Arguments
266///
267/// * `pool` - Database connection pool for executing the query
268/// * `app_id` - Unique identifier of the application
269///
270/// # Returns
271///
272/// * `Ok(i64)` - Successfully retrieved count of running instances
273/// * `Err(anyhow::Error)` - Failed to count running instances
274///
275/// # Use Cases
276///
277/// Common use cases include:
278/// - Auto-scaling decisions based on current instance count
279/// - Monitoring application capacity
280/// - Enforcing instance limits based on account tier
281/// - Billing calculations based on active instance time
282pub async fn count_running_instances(pool: &Pool<MySql>, app_id: i64) -> anyhow::Result<i64> {
283    let count = sqlx::query_scalar::<_, i64>(
284        r#"SELECT COUNT(*) FROM instances 
285        WHERE app_id = ? AND instance_status = 'running'"#,
286    )
287    .bind(app_id)
288    .fetch_one(pool)
289    .await
290    .context("Failed to count running instances")?;
291
292    Ok(count)
293}
294
295/// Terminates all running instances for a specific application.
296///
297/// This function marks all running instances of an application as 'terminated'.
298/// It's typically used during application shutdown, maintenance, or redeployment
299/// scenarios when all compute resources need to be released.
300///
301/// # Arguments
302///
303/// * `pool` - Database connection pool for executing the query
304/// * `app_id` - Unique identifier of the application whose instances to terminate
305///
306/// # Returns
307///
308/// * `Ok(())` - Successfully marked instances as terminated
309/// * `Err(anyhow::Error)` - Failed to terminate instances
310///
311/// # Important
312///
313/// This function only updates the database records to reflect termination.
314/// The actual termination of compute resources should be handled by a separate
315/// process that reacts to these status changes.
316///
317/// # Transaction Handling
318///
319/// This function uses a database transaction to ensure atomicity of the operation.
320/// If any part of the operation fails, the entire operation is rolled back.
321///
322/// # Use Cases
323///
324/// Common scenarios for using this function include:
325/// - Application shutdown or decommissioning
326/// - Emergency resource release during cost overruns
327/// - Preparing for maintenance or major version upgrades
328/// - Responding to security incidents requiring isolation
329pub async fn terminate_all_instances(pool: &Pool<MySql>, app_id: i64) -> anyhow::Result<()> {
330    let mut tx = pool.begin().await?;
331
332    sqlx::query(
333        r#"UPDATE instances 
334        SET status = 'terminated', 
335            instance_status = 'terminated',
336            updated_at = CURRENT_TIMESTAMP 
337        WHERE app_id = ? AND instance_status = 'running'"#,
338    )
339    .bind(app_id)
340    .execute(&mut *tx)
341    .await
342    .context("Failed to terminate instances")?;
343
344    tx.commit().await?;
345    Ok(())
346}