1use std::collections::HashMap;
2
3use crate::schema::{
4 historical_tasks, log_errors, log_fatals, log_infos, log_invalids, log_warnings, task_runtimes,
5 tasks,
6};
7use diesel::result::Error;
8use diesel::*;
9
10use super::RerunOptions;
11use crate::concerns::{CortexInsertable, MarkRerun};
12use crate::helpers::{NewTaskMessage, TaskReport, TaskStatus, rerun_mark};
13use crate::models::{
14 Corpus, HistoricalRun, LogError, LogFatal, LogInfo, LogInvalid, LogRecord, LogWarning,
15 NewHistoricalRun, NewLogError, NewLogFatal, NewLogInfo, NewLogInvalid, NewLogWarning, NewTask,
16 NewTaskRuntime, Service,
17};
18
19pub(crate) fn mark_imported(
20 connection: &mut PgConnection,
21 imported_tasks: &[NewTask],
22) -> Result<usize, Error> {
23 insert_into(tasks::table)
25 .values(imported_tasks)
26 .on_conflict_do_nothing()
27 .execute(connection)
28}
29
30pub(crate) fn mark_blocked(
35 connection: &mut PgConnection,
36 corpus_id_val: i32,
37 service_id_val: i32,
38) -> Result<usize, Error> {
39 update(tasks::table)
40 .filter(tasks::corpus_id.eq(corpus_id_val))
41 .filter(tasks::service_id.eq(service_id_val))
42 .filter(tasks::status.ge(0))
43 .set(tasks::status.eq(TaskStatus::Blocked(-6).raw()))
44 .execute(connection)
45}
46
47pub(crate) fn resume_blocked(
52 connection: &mut PgConnection,
53 corpus_id_val: i32,
54 service_id_val: i32,
55) -> Result<usize, Error> {
56 update(tasks::table)
57 .filter(tasks::corpus_id.eq(corpus_id_val))
58 .filter(tasks::service_id.eq(service_id_val))
59 .filter(tasks::status.lt(-5))
60 .set(tasks::status.eq(TaskStatus::TODO.raw()))
61 .execute(connection)
62}
63
64pub(crate) fn mark_all_blocked(connection: &mut PgConnection) -> Result<usize, Error> {
70 update(tasks::table)
71 .filter(tasks::status.ge(0))
72 .set(tasks::status.eq(TaskStatus::Blocked(-6).raw()))
73 .execute(connection)
74}
75
76pub(crate) fn resume_all_blocked(connection: &mut PgConnection) -> Result<usize, Error> {
79 update(tasks::table)
80 .filter(tasks::status.lt(-5))
81 .set(tasks::status.eq(TaskStatus::TODO.raw()))
82 .execute(connection)
83}
84
85pub(crate) fn mark_done(
86 connection: &mut PgConnection,
87 reports: &[TaskReport],
88) -> Result<(), Error> {
89 use crate::schema::tasks::{id, status};
90
91 let task_ids: Vec<i64> = reports.iter().map(|report| report.task.id).collect();
96 const ID_CHUNK: usize = 50_000;
105 const LOG_INSERT_CHUNK: usize = 16_000;
106 connection.transaction::<(), Error, _>(|t_connection| {
107 for ids in task_ids.chunks(ID_CHUNK) {
110 delete(log_infos::table.filter(log_infos::task_id.eq_any(ids))).execute(t_connection)?;
111 delete(log_warnings::table.filter(log_warnings::task_id.eq_any(ids)))
112 .execute(t_connection)?;
113 delete(log_errors::table.filter(log_errors::task_id.eq_any(ids))).execute(t_connection)?;
114 delete(log_fatals::table.filter(log_fatals::task_id.eq_any(ids))).execute(t_connection)?;
115 delete(log_invalids::table.filter(log_invalids::task_id.eq_any(ids)))
116 .execute(t_connection)?;
117 delete(task_runtimes::table.filter(task_runtimes::task_id.eq_any(ids)))
118 .execute(t_connection)?;
119 }
120 let mut ids_by_status: HashMap<i32, Vec<i64>> = HashMap::new();
125 let mut new_infos: Vec<NewLogInfo> = Vec::new();
126 let mut new_warnings: Vec<NewLogWarning> = Vec::new();
127 let mut new_errors: Vec<NewLogError> = Vec::new();
128 let mut new_fatals: Vec<NewLogFatal> = Vec::new();
129 let mut new_invalids: Vec<NewLogInvalid> = Vec::new();
130 let mut new_runtimes: Vec<NewTaskRuntime> = Vec::new();
134 for report in reports.iter() {
135 ids_by_status
136 .entry(report.status.raw())
137 .or_default()
138 .push(report.task.id);
139 for message in &report.messages {
140 if message.severity() == "status" {
142 continue;
143 }
144 if let NewTaskMessage::Info(record) = message {
152 let runtime_value = if record.category == "runtime_ms" {
153 Some(record.what.as_str())
154 } else if record.category == "cortex" && record.what == "runtime_ms" {
155 Some(record.details.as_str())
156 } else {
157 None
158 };
159 if let Some(value) = runtime_value
160 && let Ok(runtime_ms) = value.parse::<i32>()
161 {
162 new_runtimes.push(NewTaskRuntime {
163 task_id: report.task.id,
164 service_id: report.task.service_id,
165 runtime_ms,
166 });
167 }
168 }
169 match message {
170 NewTaskMessage::Info(record) => new_infos.push(record.clone()),
171 NewTaskMessage::Warning(record) => new_warnings.push(record.clone()),
172 NewTaskMessage::Error(record) => new_errors.push(record.clone()),
173 NewTaskMessage::Fatal(record) => new_fatals.push(record.clone()),
174 NewTaskMessage::Invalid(record) => new_invalids.push(record.clone()),
175 }
176 }
177 }
179 for (status_value, status_ids) in &ids_by_status {
183 for ids in status_ids.chunks(ID_CHUNK) {
184 update(tasks::table)
185 .filter(id.eq_any(ids))
186 .set(status.eq(*status_value))
187 .execute(t_connection)?;
188 }
189 }
190 for chunk in new_infos.chunks(LOG_INSERT_CHUNK) {
193 insert_into(log_infos::table)
194 .values(chunk)
195 .execute(t_connection)?;
196 }
197 for chunk in new_warnings.chunks(LOG_INSERT_CHUNK) {
198 insert_into(log_warnings::table)
199 .values(chunk)
200 .execute(t_connection)?;
201 }
202 for chunk in new_errors.chunks(LOG_INSERT_CHUNK) {
203 insert_into(log_errors::table)
204 .values(chunk)
205 .execute(t_connection)?;
206 }
207 for chunk in new_fatals.chunks(LOG_INSERT_CHUNK) {
208 insert_into(log_fatals::table)
209 .values(chunk)
210 .execute(t_connection)?;
211 }
212 for chunk in new_invalids.chunks(LOG_INSERT_CHUNK) {
213 insert_into(log_invalids::table)
214 .values(chunk)
215 .execute(t_connection)?;
216 }
217 for chunk in new_runtimes.chunks(LOG_INSERT_CHUNK) {
220 insert_into(task_runtimes::table)
221 .values(chunk)
222 .execute(t_connection)?;
223 }
224 Ok(())
225 })?;
226 Ok(())
227}
228
229pub(crate) fn mark_rerun<'a>(
230 connection: &'a mut PgConnection,
231 options: RerunOptions<'a>,
232) -> Result<(), Error> {
233 let RerunOptions {
234 corpus,
235 service,
236 severity_opt,
237 category_opt,
238 what_opt,
239 owner_opt,
240 description_opt,
241 } = options;
242 use crate::schema::tasks::{corpus_id, service_id, status};
243 let mut description = description_opt.unwrap_or_else(|| String::from("mark for rerun "));
245 description.push_str("(filters:");
247 if severity_opt.is_none() && category_opt.is_none() && what_opt.is_none() {
248 description.push_str(" entire corpus");
249 } else {
250 if let Some(ref severity) = severity_opt {
251 description.push_str(" severity=");
252 description.push_str(severity);
253 }
254 if let Some(ref category) = category_opt {
255 description.push_str(" category=");
256 description.push_str(category);
257 }
258 if let Some(ref what) = what_opt {
259 description.push_str(" what=");
260 description.push_str(what);
261 }
262 }
263 description.push(')');
264
265 connection.transaction::<(), Error, _>(|connection| {
274 mark_new_run(
275 connection,
276 corpus,
277 service,
278 owner_opt.unwrap_or_else(|| "admin".to_string()),
279 description,
280 )?;
281 let mark: i32 = rerun_mark();
286
287 match severity_opt {
289 Some(severity) => match category_opt {
290 Some(category) => match what_opt {
291 Some(what) => match severity.to_lowercase().as_str() {
293 "warning" => LogWarning::mark_rerun_by_what(
294 mark, corpus.id, service.id, &category, &what, connection,
295 ),
296 "error" => LogError::mark_rerun_by_what(
297 mark, corpus.id, service.id, &category, &what, connection,
298 ),
299 "fatal" => LogFatal::mark_rerun_by_what(
300 mark, corpus.id, service.id, &category, &what, connection,
301 ),
302 "invalid" => LogInvalid::mark_rerun_by_what(
303 mark, corpus.id, service.id, &category, &what, connection,
304 ),
305 _ => {
306 LogInfo::mark_rerun_by_what(mark, corpus.id, service.id, &category, &what, connection)
307 },
308 }?,
309 None => match severity.to_lowercase().as_str() {
311 "warning" => {
312 LogWarning::mark_rerun_by_category(mark, corpus.id, service.id, &category, connection)
313 },
314 "error" => {
315 LogError::mark_rerun_by_category(mark, corpus.id, service.id, &category, connection)
316 },
317 "fatal" => {
318 LogFatal::mark_rerun_by_category(mark, corpus.id, service.id, &category, connection)
319 },
320 "invalid" => {
321 LogInvalid::mark_rerun_by_category(mark, corpus.id, service.id, &category, connection)
322 },
323 _ => {
324 LogInfo::mark_rerun_by_category(mark, corpus.id, service.id, &category, connection)
325 },
326 }?,
327 },
328 None => {
329 let status_to_rerun: i32 = TaskStatus::from_key(&severity)
331 .unwrap_or(TaskStatus::NoProblem)
332 .raw();
333 update(tasks::table)
334 .filter(corpus_id.eq(corpus.id))
335 .filter(service_id.eq(service.id))
336 .filter(status.eq(status_to_rerun))
337 .set(status.eq(mark))
338 .execute(connection)?
339 },
340 },
341 None => {
342 update(tasks::table)
344 .filter(corpus_id.eq(corpus.id))
345 .filter(service_id.eq(service.id))
346 .filter(status.lt(0))
347 .set(status.eq(mark))
348 .execute(connection)?
349 },
350 };
351
352 let affected_tasks = tasks::table
356 .filter(corpus_id.eq(corpus.id))
357 .filter(service_id.eq(service.id))
358 .filter(status.eq(mark));
359 let affected_tasks_ids = affected_tasks.select(tasks::id);
360
361 let affected_log_infos = log_infos::table.filter(log_infos::task_id.eq_any(affected_tasks_ids));
362 delete(affected_log_infos).execute(connection)?;
363 let affected_log_warnings =
364 log_warnings::table.filter(log_warnings::task_id.eq_any(affected_tasks_ids));
365 delete(affected_log_warnings).execute(connection)?;
366 let affected_log_errors =
367 log_errors::table.filter(log_errors::task_id.eq_any(affected_tasks_ids));
368 delete(affected_log_errors).execute(connection)?;
369 let affected_log_fatals =
370 log_fatals::table.filter(log_fatals::task_id.eq_any(affected_tasks_ids));
371 delete(affected_log_fatals).execute(connection)?;
372 let affected_log_invalids =
373 log_invalids::table.filter(log_invalids::task_id.eq_any(affected_tasks_ids));
374 delete(affected_log_invalids).execute(connection)?;
375
376 update(affected_tasks)
378 .set(status.eq(TaskStatus::TODO.raw()))
379 .execute(connection)?;
380
381 super::rollup::invalidate_scope(connection, corpus.id, service.id)?;
385
386 Ok(())
387 })
388}
389
390pub(crate) fn mark_new_run(
391 connection: &mut PgConnection,
392 corpus: &Corpus,
393 service: &Service,
394 owner: String,
395 description: String,
396) -> Result<(), Error> {
397 mark_run_completed(connection, corpus, service)?;
399 let hrun = NewHistoricalRun {
401 corpus_id: corpus.id,
402 service_id: service.id,
403 description,
404 owner,
405 };
406 hrun.create(connection)?;
407 Ok(())
415}
416
417fn mark_run_completed(
418 connection: &mut PgConnection,
419 corpus: &Corpus,
420 service: &Service,
421) -> Result<(), Error> {
422 let to_finish: Vec<HistoricalRun> = HistoricalRun::find_by(corpus, service, connection)?
423 .into_iter()
424 .filter(|run| run.end_time.is_none())
425 .collect();
426 if !to_finish.is_empty() {
427 connection.transaction::<(), Error, _>(move |t_connection| {
428 for run in to_finish.into_iter() {
429 run.mark_completed(t_connection)?;
430 }
431 Ok(())
432 })?;
433 }
434 Ok(())
435}
436
437pub fn save_historical_tasks(
438 connection: &mut PgConnection,
439 corpus: &Corpus,
440 service: &Service,
441) -> Result<usize, Error> {
442 snapshot_tasks(connection, corpus.id, service.id)
443}
444
445pub fn snapshot_tasks(
451 connection: &mut PgConnection,
452 corpus_id: i32,
453 service_id: i32,
454) -> Result<usize, Error> {
455 insert_into(historical_tasks::table)
456 .values(
457 tasks::table
458 .select((tasks::id, tasks::status))
459 .filter(tasks::corpus_id.eq(corpus_id))
460 .filter(tasks::service_id.eq(service_id)),
461 )
462 .into_columns((historical_tasks::task_id, historical_tasks::status))
463 .execute(connection)
464}