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

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