assemblyline-core 4.7.5.dev89__tar.gz → 4.7.5.dev94__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/PKG-INFO +1 -1
  2. assemblyline_core-4.7.5.dev94/assemblyline_core/VERSION +1 -0
  3. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/expiry/run_expiry.py +144 -60
  4. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core.egg-info/PKG-INFO +1 -1
  5. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_expiry.py +9 -1
  6. assemblyline_core-4.7.5.dev89/assemblyline_core/VERSION +0 -1
  7. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/LICENCE.md +0 -0
  8. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/README.md +0 -0
  9. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/__init__.py +0 -0
  10. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/alerter/__init__.py +0 -0
  11. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/alerter/processing.py +0 -0
  12. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/alerter/run_alerter.py +0 -0
  13. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/archiver/__init__.py +0 -0
  14. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/archiver/run_archiver.py +0 -0
  15. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/badlist_client.py +0 -0
  16. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/dispatching/__init__.py +0 -0
  17. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/dispatching/client.py +0 -0
  18. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/dispatching/dispatcher.py +0 -0
  19. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/dispatching/schedules.py +0 -0
  20. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/expiry/__init__.py +0 -0
  21. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/ingester/__init__.py +0 -0
  22. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/ingester/constants.py +0 -0
  23. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/ingester/ingester.py +0 -0
  24. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/__init__.py +0 -0
  25. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/es_metrics.py +0 -0
  26. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/heartbeat_formatter.py +0 -0
  27. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/helper.py +0 -0
  28. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/metrics_server.py +0 -0
  29. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
  30. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
  31. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
  32. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/__init__.py +0 -0
  33. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/client.py +0 -0
  34. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/creator/__init__.py +0 -0
  35. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/creator/run.py +0 -0
  36. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/creator/run_worker.py +0 -0
  37. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/loader/__init__.py +0 -0
  38. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/loader/run.py +0 -0
  39. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/loader/run_worker.py +0 -0
  40. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/replay/replay.py +0 -0
  41. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/safelist_client.py +0 -0
  42. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/__init__.py +0 -0
  43. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/base.py +0 -0
  44. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/collection.py +0 -0
  45. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/controllers/__init__.py +0 -0
  46. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/controllers/docker_ctl.py +0 -0
  47. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/controllers/interface.py +0 -0
  48. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/controllers/kubernetes.py +0 -0
  49. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/controllers/kubernetes_ctl.py +0 -0
  50. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/run_scaler.py +0 -0
  51. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler/scaler_server.py +0 -0
  52. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler_hpa/__init__.py +0 -0
  53. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler_hpa/kubernetes.py +0 -0
  54. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler_hpa/run_scaler.py +0 -0
  55. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/scaler_hpa/scaler_server.py +0 -0
  56. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/server_base.py +0 -0
  57. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/signature_client.py +0 -0
  58. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/submission_client.py +0 -0
  59. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/updater/__init__.py +0 -0
  60. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/updater/helper.py +0 -0
  61. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/updater/run_updater.py +0 -0
  62. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/__init__.py +0 -0
  63. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/crawler.py +0 -0
  64. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/department_map.py +0 -0
  65. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/safelist.py +0 -0
  66. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/stream_map.py +0 -0
  67. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/vacuum/worker.py +0 -0
  68. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/workflow/__init__.py +0 -0
  69. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core/workflow/run_workflow.py +0 -0
  70. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core.egg-info/SOURCES.txt +0 -0
  71. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core.egg-info/dependency_links.txt +0 -0
  72. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core.egg-info/requires.txt +0 -0
  73. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/assemblyline_core.egg-info/top_level.txt +0 -0
  74. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/setup.cfg +0 -0
  75. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/setup.py +0 -0
  76. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_alerter.py +0 -0
  77. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_badlist_client.py +0 -0
  78. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_replay.py +0 -0
  79. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_safelist_client.py +0 -0
  80. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_scaler.py +0 -0
  81. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_scheduler.py +0 -0
  82. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_signature_client.py +0 -0
  83. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_updater_helper.py +0 -0
  84. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_vacuum.py +0 -0
  85. {assemblyline_core-4.7.5.dev89 → assemblyline_core-4.7.5.dev94}/test/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assemblyline-core
3
- Version: 4.7.5.dev89
3
+ Version: 4.7.5.dev94
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -0,0 +1 @@
1
+ 4.7.5.dev94
@@ -15,7 +15,7 @@ from datemath import dm
15
15
  from assemblyline_core.server_base import ServerBase
16
16
  from assemblyline_core.dispatching.dispatcher import BAD_SID_HASH
17
17
  from assemblyline.common import forge, chunk
18
- from assemblyline.common.isotime import epoch_to_iso, now_as_iso
18
+ from assemblyline.common.isotime import epoch_to_iso, now_as_iso, now
19
19
  from assemblyline.common.metrics import MetricsFactory
20
20
  from assemblyline.filestore import FileStore
21
21
  from assemblyline.odm.messages.expiry_heartbeat import Metrics
@@ -26,6 +26,9 @@ from assemblyline.remote.datatypes.set import Set
26
26
  if TYPE_CHECKING:
27
27
  from assemblyline.datastore.collection import ESCollection
28
28
 
29
+ QUERY_DELETE_SIZE = 100_000
30
+ QUERY_WORKER_CHECK_VOLUME = 5_000_000
31
+
29
32
 
30
33
  def file_delete_worker(logger, filestore_urls, file_batch: list[tuple[str, bool]],
31
34
  archive_filestore_urls=None) -> list[tuple[str, bool]]:
@@ -215,12 +218,6 @@ class ExpiryManager(ServerBase):
215
218
  if not hot_file_list and not archive_file_list:
216
219
  self.log.warning(f'[{collection.name}] Expiry unable to clean up any of the files in filestore.')
217
220
 
218
- def _simple_delete(self, collection: ESCollection, delete_query, number_to_delete):
219
- self.heartbeat()
220
- collection.delete_by_query(delete_query, index_type=self.index_type)
221
- self.counter.increment(f'{collection.name}', increment_by=number_to_delete)
222
- self.log.info(f"[{collection.name}] Deleted {number_to_delete} items from the datastore...")
223
-
224
221
  def _cleanup_canceled_submission(self, sid):
225
222
  # Allowing us at minimum 5 minutes to cleanup the submission
226
223
  self.heartbeat(int(time.time() + 5 * 60))
@@ -246,53 +243,47 @@ class ExpiryManager(ServerBase):
246
243
  # blob would be deleted anyway, leaving a file record with no filestore object
247
244
  delete_query = f"expiry_ts:{{{start} TO {end}]"
248
245
 
249
- # check if we are dealing with an index that needs file cleanup
250
- if self.config.core.expiry.delete_storage and collection.name in self.fs_hashmap:
251
- # Delete associated files
252
- delete_objects: list[tuple[str, bool]] = []
253
- for item in collection.stream_search(
254
- delete_query, fl='id,from_archive', as_obj=False, index_type=self.index_type):
255
- self.heartbeat()
256
- delete_objects.append((item['id'], item.get('from_archive', False)))
257
-
258
- # Filter archived documents if archive filestore is the same as the filestore
259
- expire_only: list[tuple[str, bool]] = []
260
- if self.same_storage and self.archive_access and collection.name == 'file':
261
- # Separate hot and archive files
262
- delete_from_archive = [i[0] for i in delete_objects if i[1]]
263
- delete_from_hot = [i[0] for i in delete_objects if not i[1]]
264
-
265
- # Check for overlap
266
- overlap = set(delete_from_archive).intersection(set(delete_from_hot))
267
- delete_from_archive = list(set(delete_from_archive)-overlap)
268
- delete_from_hot = list(set(delete_from_hot)-overlap)
269
-
270
- # Create the original delete_object form the overlap
271
- delete_objects = [(k, False) for k in overlap]
272
- delete_objects.extend([(k, True) for k in overlap])
273
-
274
- if delete_from_hot:
275
- # Check hot objects to delete if they are in archive
276
- archived_files = self.datastore.file.multiexists(delete_from_hot, index_type=Index.ARCHIVE)
277
- delete_objects.extend([(k, False) for k, v in archived_files.items() if not v])
278
- expire_only.extend([(k, False) for k, v in archived_files.items() if v])
279
-
280
- if delete_from_archive:
281
- # Check hot objects to delete if they are in archive
282
- hot_files = self.datastore.file.multiexists(delete_from_archive, index_type=Index.HOT)
283
- delete_objects.extend([(k, True) for k, v in hot_files.items() if not v])
284
- expire_only.extend([(k, True) for k, v in hot_files.items() if v])
285
-
286
- delete_tasks = self.fs_hashmap[collection.name](delete_objects, final_date)
287
-
288
- # Proceed with deletion, but only after all the scheduled deletes for this
289
- self.log.info(f"[{collection.name}] Scheduled {len(delete_objects)}/{number_to_delete} files to be "
290
- f"removed from the {'cachestore' if 'cache' in collection.name else 'filestore'}")
291
- self._finish_delete(collection, delete_tasks, expire_only)
292
-
293
- else:
294
- # Proceed with deletion
295
- self._simple_delete(collection, delete_query, number_to_delete)
246
+ # Delete associated files
247
+ delete_objects: list[tuple[str, bool]] = []
248
+ for item in collection.stream_search(
249
+ delete_query, fl='id,from_archive', as_obj=False, index_type=self.index_type):
250
+ self.heartbeat()
251
+ delete_objects.append((item['id'], item.get('from_archive', False)))
252
+
253
+ # Filter archived documents if archive filestore is the same as the filestore
254
+ expire_only: list[tuple[str, bool]] = []
255
+ if self.same_storage and self.archive_access and collection.name == 'file':
256
+ # Separate hot and archive files
257
+ delete_from_archive = [i[0] for i in delete_objects if i[1]]
258
+ delete_from_hot = [i[0] for i in delete_objects if not i[1]]
259
+
260
+ # Check for overlap
261
+ overlap = set(delete_from_archive).intersection(set(delete_from_hot))
262
+ delete_from_archive = list(set(delete_from_archive)-overlap)
263
+ delete_from_hot = list(set(delete_from_hot)-overlap)
264
+
265
+ # Create the original delete_object form the overlap
266
+ delete_objects = [(k, False) for k in overlap]
267
+ delete_objects.extend([(k, True) for k in overlap])
268
+
269
+ if delete_from_hot:
270
+ # Check hot objects to delete if they are in archive
271
+ archived_files = self.datastore.file.multiexists(delete_from_hot, index_type=Index.ARCHIVE)
272
+ delete_objects.extend([(k, False) for k, v in archived_files.items() if not v])
273
+ expire_only.extend([(k, False) for k, v in archived_files.items() if v])
274
+
275
+ if delete_from_archive:
276
+ # Check hot objects to delete if they are in archive
277
+ hot_files = self.datastore.file.multiexists(delete_from_archive, index_type=Index.HOT)
278
+ delete_objects.extend([(k, True) for k, v in hot_files.items() if not v])
279
+ expire_only.extend([(k, True) for k, v in hot_files.items() if v])
280
+
281
+ delete_tasks = self.fs_hashmap[collection.name](delete_objects, final_date)
282
+
283
+ # Proceed with deletion, but only after all the scheduled deletes for this
284
+ self.log.info(f"[{collection.name}] Scheduled {len(delete_objects)}/{number_to_delete} files to be "
285
+ f"removed from the {'cachestore' if 'cache' in collection.name else 'filestore'}")
286
+ self._finish_delete(collection, delete_tasks, expire_only)
296
287
 
297
288
  def feed_expiry_jobs(self, collection, start, jobs: list[concurrent.futures.Future],
298
289
  pool: ThreadPoolExecutor) -> tuple[str, bool]:
@@ -329,11 +320,11 @@ class ExpiryManager(ServerBase):
329
320
  return start, number_to_delete < self.expiry_size
330
321
 
331
322
  def _get_final_date(self):
332
- now = now_as_iso()
323
+ _now = now_as_iso()
333
324
  if self.config.core.expiry.batch_delete:
334
- final_date = dm(f"{now}||-{self.config.core.expiry.delay}h/d").float_timestamp
325
+ final_date = dm(f"{_now}||-{self.config.core.expiry.delay}h/d").float_timestamp
335
326
  else:
336
- final_date = dm(f"{now}||-{self.config.core.expiry.delay}h").float_timestamp
327
+ final_date = dm(f"{_now}||-{self.config.core.expiry.delay}h").float_timestamp
337
328
  return epoch_to_iso(final_date)
338
329
 
339
330
  def _get_next_chunk(self, collection: ESCollection, start, final_date):
@@ -357,7 +348,11 @@ class ExpiryManager(ServerBase):
357
348
 
358
349
  # Launch threads that expire data from each collection of data
359
350
  for collection in self.expirable_collections:
360
- thread = threading.Thread(target=self.run_collection, args=[pool, collection])
351
+ # check if we are dealing with an index that needs file cleanup
352
+ if self.config.core.expiry.delete_storage and collection.name in self.fs_hashmap:
353
+ thread = threading.Thread(target=self.run_file_collection, args=[pool, collection])
354
+ else:
355
+ thread = threading.Thread(target=self.run_collection, args=[collection])
361
356
  thread.start()
362
357
  main_threads.append(thread)
363
358
 
@@ -376,8 +371,13 @@ class ExpiryManager(ServerBase):
376
371
  pool.submit(self.log_errors(self._cleanup_canceled_submission), submission.sid)
377
372
  self.sleep_with_heartbeat(self.config.core.expiry.sleep_time)
378
373
 
379
- def run_collection(self, pool: concurrent.futures.ThreadPoolExecutor, collection):
380
- """Feed batches of jobs to delete to the thread pool for the given collection."""
374
+ def run_file_collection(self, pool: concurrent.futures.ThreadPoolExecutor, collection):
375
+ """
376
+ Feed batches of jobs to delete to the thread pool for the given collection.
377
+
378
+ The files must be cleaned up for this collection so we operate by
379
+ batching rather than delete by query.
380
+ """
381
381
  start = "*"
382
382
  jobs: list[concurrent.futures.Future] = []
383
383
 
@@ -412,6 +412,90 @@ class ExpiryManager(ServerBase):
412
412
  self.log.error("File delete worker pool crashed.")
413
413
  self.file_delete_worker = ProcessPoolExecutor(self.config.core.expiry.delete_workers)
414
414
 
415
+ def run_collection(self, collection):
416
+ """
417
+ For collections where no file cleanup is needed we can simply run delete by query.
418
+
419
+ In cases where there are large quantities of records expiring (or a large backlog) multiple
420
+ delete by query calls running on non-overlapping sections of the data can be needed to catch
421
+ up.
422
+
423
+ The metric we use here is that every day gets its own delete query. When the expiry daemon
424
+ starts we will probe to see how many days of historical data we need to clean up and
425
+ start that many query workers.
426
+
427
+ Perodically a workers will terminate and this calculation will be redone.
428
+ """
429
+ while self.running:
430
+ # Calculate how many queries we want to run
431
+ queries = self.day_chunks(collection)
432
+
433
+ # prepare a thread pool suitable for that operation
434
+ with ThreadPoolExecutor(len(queries)) as pool:
435
+ # Prepare a signal so we can stop all the workers as desired
436
+ stop = threading.Event()
437
+
438
+ # dispatch each of these queries
439
+ futures = [pool.submit(self.run_collection_query, collection, stop, query) for query in queries]
440
+
441
+ # wait for one of them to finish
442
+ for future in as_completed(futures):
443
+ stop.set()
444
+ future.result()
445
+
446
+ def day_chunks(self, collection):
447
+ # Figure out the range of time we want to build queries for
448
+ earliest = self.get_earliest_expiring(collection)
449
+ if not earliest:
450
+ return []
451
+ days = int((now() - earliest)/(60 * 60 * 24)) + 1
452
+
453
+ # Base no settings we will truncade expiry ranges by the day
454
+ if self.config.core.expiry.batch_delete:
455
+ suffix = f"-{self.config.core.expiry.delay}h/d"
456
+ else:
457
+ suffix = f"-{self.config.core.expiry.delay}h"
458
+
459
+ # construct the ranges that build queries covering all of those days, bounded by
460
+ # the appropirately modifide NOW on the high end, and modfied to be open on the low end
461
+ ranges = [(f"now-1d{suffix}", f"now{suffix}")]
462
+ for days in range(1, days):
463
+ ranges.append((f"now-{days + 1}d{suffix}", f"now-{days}d{suffix}"))
464
+ ranges[-1] = ("*", ranges[-1][1])
465
+
466
+ # Convert those ranges to queries, each inclusive on the low end and exclusive on the high end
467
+ queries = [f"expiry_ts: [{s} TO {e}}}" for s, e in ranges]
468
+ return queries
469
+
470
+ def get_earliest_expiring(self, collection) -> None | float:
471
+ final = self._get_final_date()
472
+ rows = collection.search(f"expiry_ts: [* TO {final}]", rows=1, fl='expiry_ts', sort="expiry_ts asc")
473
+ if rows['items']:
474
+ return rows['items'][0]['expiry_ts'].timestamp()
475
+ return None
476
+
477
+ def run_collection_query(self, collection, stop, query):
478
+ total_deleted = 0
479
+ while self.running or stop.is_set():
480
+ self.heartbeat()
481
+ deleted = 0
482
+
483
+ try:
484
+ deleted = collection.simple_delete_by_query(query, sort='expiry_ts asc', max_docs=QUERY_DELETE_SIZE)
485
+ total_deleted += deleted
486
+ self.counter.increment(f'{collection.name}', increment_by=deleted)
487
+ self.log.info(f"[{collection.name}] Deleted {deleted} items from the datastore...")
488
+
489
+ except Exception as e:
490
+ self.log.exception(str(e))
491
+
492
+ if total_deleted >= QUERY_WORKER_CHECK_VOLUME:
493
+ return
494
+
495
+ # If the number deleted is small wait before running the delete command again
496
+ if deleted < QUERY_DELETE_SIZE * 0.9:
497
+ self.sleep_with_heartbeat(self.config.core.expiry.sleep_time)
498
+
415
499
 
416
500
  if __name__ == "__main__":
417
501
  with ExpiryManager() as em:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assemblyline-core
3
- Version: 4.7.5.dev89
3
+ Version: 4.7.5.dev94
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -1,12 +1,14 @@
1
1
 
2
2
  import pytest
3
3
  import random
4
+ import threading
4
5
  import concurrent.futures
5
6
 
6
7
  from assemblyline.common.isotime import now_as_iso
7
8
  from assemblyline.datastore.helper import AssemblylineDatastore
8
9
  from assemblyline.odm.randomizer import random_model_obj
9
10
 
11
+ import assemblyline_core.expiry.run_expiry
10
12
  from assemblyline_core.expiry.run_expiry import ExpiryManager
11
13
 
12
14
  MAX_OBJECTS = 10
@@ -60,9 +62,15 @@ def test_expire_all(config, ds_expiry, filestore):
60
62
  expiry = ExpiryManager(config=config, datastore=ds_expiry, filestore=filestore)
61
63
  expiry.running = True
62
64
  expiry.counter = FakeCounter()
65
+
66
+ assemblyline_core.expiry.run_expiry.QUERY_WORKER_CHECK_VOLUME = 0
67
+
63
68
  with concurrent.futures.ThreadPoolExecutor(5) as pool:
64
69
  for collection in expiry.expirable_collections:
65
- expiry.feed_expiry_jobs(collection=collection, pool=pool, start='*', jobs=[])
70
+ if collection.name in expiry.fs_hashmap:
71
+ expiry.feed_expiry_jobs(collection=collection, pool=pool, start='*', jobs=[])
72
+ else:
73
+ pool.submit(expiry.run_collection, collection)
66
74
 
67
75
  for k, v in expiry_collections_len.items():
68
76
  assert v == expiry.counter.get(k)
@@ -1 +0,0 @@
1
- 4.7.5.dev89