1use core::fmt;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use surrealdb::RecordId;
5use crate::database::Db;
6use std::sync::Arc;
7use tracing::{info, error, instrument};
8use std::sync::atomic::{AtomicBool, Ordering};
9use crate::Command;
10
11#[derive(Debug,Clone, PartialEq, Deserialize, Serialize)]
17pub enum AgentKind {
18 Queue,
19 Scheduler,
20 Task,
21 None
22}
23
24impl fmt::Display for AgentKind {
25
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Self::Scheduler => write!(f,"Scheduler"),
29 Self::Queue => write!(f,"Queue"),
30 Self::Task => write!(f,"Task"),
31 Self::None => write!(f,"None")
32 }
33 }
34}
35
36#[derive(Debug,Clone, PartialEq, Deserialize, Serialize)]
44pub enum AgentStatus {
45 Initialized,
46 Running,
47 Terminated,
48 Error,
49 Completed
50}
51
52impl fmt::Display for AgentStatus {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Self::Initialized => write!(f,"Initialized"),
56 Self::Running => write!(f,"Running"),
57 Self::Terminated => write!(f,"Terminated"),
58 Self::Error => write!(f,"Error"),
59 Self::Completed => write!(f,"Completed"),
60 }
61 }
62}
63
64#[derive(Debug,Clone,Deserialize,Serialize,PartialEq)]
72pub struct AgentData {
73 #[serde(skip_serializing_if="Option::is_none")]
74 pub id: Option<RecordId>,
75 #[serde(skip_serializing_if="Option::is_none")]
76 pub parent_id: Option<RecordId>,
77 #[serde(skip_serializing_if="Option::is_none")]
78 pub queue_id: Option<RecordId>,
79 #[serde(skip_serializing_if="Option::is_none")]
80 pub name: Option<String>,
81 #[serde(skip_serializing_if="Option::is_none")]
82 pub kind: Option<AgentKind>,
83 #[serde(skip_serializing_if="Option::is_none")]
84 pub server: Option<String>,
85 #[serde(skip_serializing_if="Option::is_none")]
86 pub task_id: Option<u64>,
87 #[serde(skip_serializing_if="Option::is_none")]
88 pub status: Option<AgentStatus>,
89 #[serde(skip_serializing_if="Option::is_none")]
90 pub command: Option<Command>,
91 #[serde(skip_serializing_if="Option::is_none")]
92 pub command_is_executed: Option<bool>,
93 #[serde(skip_serializing_if="Option::is_none")]
94 pub message: Option<String>,
95 #[serde(skip_serializing_if="Option::is_none")]
96 pub author: Option<String>,
97 pub date_modified: DateTime<Utc>
98}
99
100impl Default for AgentData {
101 fn default() -> Self {
102 Self {
103 id: None,
104 parent_id: None,
105 queue_id: None,
106 name: None,
107 kind: None,
108 server: None,
109 task_id: None,
110 status: None,
111 command: None,
112 command_is_executed: None,
113 message: None,
114 author: None,
115 date_modified: Utc::now()
116 }
117 }
118}
119
120#[derive(Debug,Clone,Deserialize,Serialize, Default)]
127pub struct AgentFilter {
128 pub id: Option<RecordId>,
129 pub ids: Option<Vec<RecordId>>,
130 pub parent_id: Option<RecordId>,
131 pub parent_ids: Option<Vec<RecordId>>,
132 pub queue_id: Option<RecordId>,
133 pub queue_ids: Option<Vec<RecordId>>,
134 pub name: Option<String>,
135 pub names: Option<Vec<String>>,
136 pub kind: Option<AgentKind>,
137 pub server: Option<String>,
138 pub task_id: Option<u64>,
139 pub status: Option<AgentStatus>,
140 pub statuses: Option<Vec<AgentStatus>>,
141 pub command: Option<Command>,
142 pub commands: Option<Vec<Command>>,
143 pub command_is_executed: Option<bool>,
144 pub message: Option<String>,
145}
146
147#[derive(Debug, Clone)]
148pub struct Agent {
149 db: Arc<Db>,
150 table: String,
151}
152
153static AGENT_TABLE_CREATED: AtomicBool = AtomicBool::new(false);
154
155impl Agent {
156
157 pub async fn new(db: Option<Arc<Db>>) -> Self {
172 let db: Arc<Db> = if let Some(value) = db {
173 value.clone()
174 }
175 else {
176 Arc::new(Db::new(None).await.unwrap())
177 };
178 let table: String = "kafru_agents".to_string();
179 Self {
180 db,
181 table
182 }
183 }
184
185 #[instrument(skip_all)]
199 pub async fn purge(&self,server: String) -> Result<bool,String> {
200 match self.db.client.query("DELETE type::table($table) WHERE server = $server")
201 .bind(("table",self.table.clone()))
202 .bind(("server",server.clone()))
203 .await {
204 Ok(_) => {
205 Ok(true)
206 },
207 Err(error) => {
208 error!("{}",error);
209 Err(format!("unable to purge {} for server {}",self.table,server))
210 }
211 }
212 }
213
214 #[instrument(skip_all)]
229 pub async fn create_table(&self, server: String) -> Result<bool,String> {
230 let is_created: bool = AGENT_TABLE_CREATED.load(Ordering::Relaxed);
231 if is_created {
232 return Ok(true);
233 }
234 let stmt: String = format!("DEFINE TABLE IF NOT EXISTS {};",self.table);
235 match self.db.client.query(stmt).await {
236 Ok(_) => {
237 info!("table {} has been created",&self.table);
238 if let Err(error) = self.purge(server).await {
239 return Err(error);
240 }
241 AGENT_TABLE_CREATED.store(true, Ordering::Relaxed);
242 Ok(true)
243 }
244 Err(error) => {
245 error!("{}",error);
246 Err(format!("unable to create table {}",&self.table))
247 }
248 }
249 }
250
251 pub async fn remove(&self,id: RecordId, include_related: bool) -> Result<bool,String> {
268 match self.db.client.delete::<Option<AgentData>>(id.clone()).await {
269 Ok(_) => {
270 if include_related {
271 if let Err(error) = self.db.client.query("DELETE FROM type::table($table) WHERE parent_id=type::thing($parent_id)")
272 .bind(("table",self.table.clone()))
273 .bind(("parent_id",id.clone())).await {
274 error!("{}",error);
275 return Err(format!("database error when deleting related data of agent data with id: {:?}",id));
276 }
277 }
278 Ok(true)
279 }
280 Err(error) => {
281 error!("{}",error);
282 Err(format!("database error when deleting agent data with id: {:?}",id))
283 }
284 }
285 }
286
287 pub async fn get(&self,id: RecordId) -> Result<AgentData, String> {
300
301 match self.db.client.select::<Option<AgentData>>(id.clone()).await {
302 Ok(data) => {
303 if let Some(item) = data {
304 return Ok(item);
305 }
306 return Err(format!("unable to find agent with id: {:?}",id));
307 }
308 Err(error) => {
309 error!("{}",error);
310 Err(format!("database error when retrieving agent data with id: {:?}",id))
311 }
312 }
313 }
314
315 pub async fn get_by_name(&self,name: String,server: String) -> Result<AgentData, String> {
329 match self.db.client.query("SELECT * FROM type::table($table) WHERE name=$name AND server=$server")
330 .bind(("table",self.table.clone()))
331 .bind(("name",name.clone()))
332 .bind(("server",server.clone())).await {
333 Ok(mut response) => {
334 if let Ok(data) = response.take::<Option<AgentData>>(0) {
335 if let Some(item) = data {
336 return Ok(item);
337 }
338 }
339 return Err(format!("unable to find agent with name: {}, server: {}",name, server));
340 }
341 Err(error) => {
342 error!("{}",error);
343 Err(format!("database error when retrieving agent data with name:{}, server: {}",name, server))
344 }
345 }
346 }
347
348 pub async fn list(&self,filters: AgentFilter) -> Result<Vec<AgentData>,String> {
361 let mut stmt: String = "SELECT * FROM type::table($table)".to_string();
362 let mut where_stmt: Vec<String> = Vec::new();
363
364 if filters.id.is_some() {
366 where_stmt.push("type::thing(id)=$id".to_string());
367 }
368 if filters.parent_id.is_some() {
369 where_stmt.push("parent_id=type::thing($parent_id)".to_string());
370 }
371 if filters.command.is_some() {
372 where_stmt.push("command=$command".to_string());
373 }
374 if filters.command_is_executed.is_some() {
375 where_stmt.push("command_is_executed=$command_is_executed".to_string());
376 }
377 if filters.kind.is_some() {
378 where_stmt.push("kind=$kind".to_string());
379 }
380 if filters.name.is_some() {
381 where_stmt.push("name=$name".to_string());
382 }
383 if filters.queue_id.is_some() {
384 where_stmt.push("queue_id=$queue_id".to_string());
385 }
386 if filters.server.is_some() {
387 where_stmt.push("server=$server".to_string());
388 }
389 if filters.status.is_some() {
390 where_stmt.push("status=$status".to_string());
391 }
392 if filters.statuses.is_some() {
393 where_stmt.push("status IN $statuses".to_string());
394 }
395 if filters.commands.is_some() {
396 where_stmt.push("command IN $commands".to_string());
397 }
398 if filters.names.is_some() {
399 where_stmt.push("name IN $names".to_string());
400 }
401 if filters.queue_ids.is_some() {
402 where_stmt.push("queue_id IN $queue_ids".to_string());
403 }
404 if filters.ids.is_some() {
405 where_stmt.push("id IN $ids".to_string());
406 }
407 if !where_stmt.is_empty() {
408 stmt = format!("{} WHERE {}",stmt,where_stmt.join(" AND "));
409 }
410
411 let mut query = self.db.client.query(stmt).bind(("table",self.table.clone()));
413
414 if let Some(value) = filters.id {
415 query = query.bind(("id",value));
416 }
417 if let Some(value) = filters.parent_id {
418 query = query.bind(("parent_id",value));
419 }
420 if let Some(value) = filters.command {
421 query = query.bind(("command",value));
422 }
423 if let Some(value) = filters.command_is_executed {
424 query = query.bind(("command_is_executed",value));
425 }
426 if let Some(value) = filters.kind {
427 query = query.bind(("kind",value));
428 }
429 if let Some(value) = filters.name {
430 query = query.bind(("name",value));
431 }
432 if let Some(value) = filters.queue_id {
433 query = query.bind(("queue_id",value));
434 }
435 if let Some(value) = filters.server {
436 query = query.bind(("server",value));
437 }
438 if let Some(value) = filters.status {
439 query = query.bind(("status",value));
440 }
441 if let Some(values) = filters.statuses {
442 query = query.bind(("statuses",values));
443 }
444 if let Some(values) = filters.commands {
445 query = query.bind(("commands",values));
446 }
447 if let Some(values) = filters.queue_ids {
448 query = query.bind(("queue_ids",values));
449 }
450 if let Some(values) = filters.ids {
451 query = query.bind(("ids",values));
452 }
453 if let Some(values) = filters.names {
454 query = query.bind(("names",values));
455 }
456 match query.await {
457 Ok(mut response) => {
458 if let Ok(data) = response.take::<Vec<AgentData>>(0) {
459 return Ok(data);
460 }
461 return Err(format!("unable to retrive agent data"));
462 }
463 Err(error) => {
464 error!("{}",error);
465 return Err("database error when retrieving agent data".to_string());
466 }
467 }
468 }
469
470 pub async fn update(&self,id: RecordId, data:AgentData) -> Result<AgentData,String> {
486 match self.get(id.clone()).await {
487 Ok(record)=> {
488 let data: AgentData = AgentData {
489 name: if data.name.is_none() { record.name } else { data.name },
490 server: if data.server.is_none() { record.server } else {data.server },
491 parent_id: if data.parent_id.is_none() { record.parent_id } else {data.parent_id },
492 kind: if data.kind.is_none() { record.kind } else {data.kind },
493 queue_id: if data.queue_id.is_none() { record.queue_id } else {data.queue_id },
494 status: if data.status.is_none() { record.status } else {data.status },
495 message: if data.message.is_none() { record.message } else {data.message },
496 author: if data.author.is_none() { record.author } else {data.author },
497 command: if data.command.is_none() { record.command } else {data.command },
498 command_is_executed: if data.command_is_executed.is_none() { record.command_is_executed } else {data.command_is_executed },
499 task_id: if data.task_id.is_none() { record.task_id } else {data.task_id },
500 date_modified: Utc::now(),
501 ..Default::default()
502 };
503 match self.db.client.update::<Option<AgentData>>(id).content(data.clone()).await {
504 Ok(response) => {
505 if let Some(data) = response {
506 return Ok(data);
507 }
508 Err(format!("agent {} under server {} not found",data.name.unwrap(),data.server.unwrap()))
509 }
510 Err(error) => {
511 error!("{}",error);
512 Err(format!("unable to update agent {} under server {}",data.name.unwrap(),data.server.unwrap()))
513 }
514 }
515 }
516 Err(error) => Err(error.to_string())
517 }
518 }
519
520 pub async fn send_command(&self,id: RecordId, command:Command, message: Option<String>, author: Option<String>) -> Result<AgentData,String> {
536
537 let data: AgentData = AgentData {
538 command: Some(command),
539 command_is_executed: Some(false),
540 message,
541 author,
542 ..Default::default()
543 };
544 match self.db.client.update::<Option<AgentData>>(id).merge(data.clone()).await {
545 Ok(response) => {
546 if let Some(data) = response {
547 return Ok(data);
548 }
549 Err(format!("agent record not found"))
550 }
551 Err(error) => {
552 error!("{}",error);
553 Err(format!("unable to send command to agent"))
554 }
555 }
556 }
557 pub async fn register(&self, mut data: AgentData) -> Result<AgentData,String>{
571 if let Some(value) = data.name.clone() {
572 if value.len() < 3 {
573 return Err("name must be atleast more than 3 characters".to_string());
574 }
575 }
576 else {
577 return Err("name is required".to_string());
578 }
579 if data.kind.is_none() {
580 return Err("kind is required".to_string())
581 }
582 if data.task_id == Some(0) && data.kind == Some(AgentKind::Task) {
583 return Err(format!("task_id must be greater than 0 for task got {}",data.task_id.unwrap()));
584 }
585 if data.server.is_none() {
586 return Err(format!("server is required"));
587 }
588 if let Err(error) = self.create_table(data.server.clone().unwrap()).await {
589 return Err(error);
590 }
591 if data.command_is_executed.is_none() {
592 data.command_is_executed = Some(false);
593 }
594 if let Ok(items) = self.list(AgentFilter {
595 server: data.server.clone(),
596 name: data.name.clone(),
597 ..Default::default()
598 }).await {
599 if items.len() > 0 {
600 if let Some(item) = items.first() {
601 self.remove(item.id.clone().unwrap(),true).await?;
602 }
603 }
604 }
605 match self.db.client.create::<Option<AgentData>>(self.table.clone()).content(data.clone()).await {
606 Ok(response) => {
607 if let Some(data) = response {
608 return Ok(data);
609 }
610 return Err(format!("no data found for agent {} at server {}",data.name.unwrap(),data.server.unwrap()))
611 }
612 Err(error) => {
613 error!("{}",error);
614 Err(format!("unable to register agent {} at server {}",data.name.unwrap(),data.server.unwrap()))
615 }
616 }
617 }
618
619
620 pub async fn to_id(id: tokio::task::Id) -> u64 {
628 id.to_string().parse::<u64>().unwrap()
629 }
630
631 pub async fn to_name(queue_name: &String, task_id: &u64) -> String {
640 format!("{}-{}", queue_name, task_id)
641 }
642}
643