def test_task_server(self): # Create some stats t_now = datetime.utcnow() dm.save_object(SystemStats( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, t_now - timedelta(minutes=5), t_now )) # We should now have stats sys_stats = dm.search_system_stats(t_now - timedelta(minutes=60), t_now) self.assertGreater(len(sys_stats), 0) # Check current task list entries tasks = dm.list_objects(Task, order_field=Task.id) task_list_len = len(tasks) # Post a background task to purge the stats KEEP_SECS = 10 task_obj = tm.add_task( None, 'Purge system statistics', 'purge_system_stats', {'before_time': t_now}, Task.PRIORITY_NORMAL, 'info', 'error', KEEP_SECS ) self.assertIsNotNone(task_obj) # Check it really got added tasks = dm.list_objects(Task, order_field=Task.id) self.assertEqual(len(tasks), task_list_len + 1) task_list_len = len(tasks) # Post it again, make sure there is no dupe added dupe_task_obj = tm.add_task( None, 'Purge system statistics', 'purge_system_stats', {'before_time': t_now}, Task.PRIORITY_NORMAL, 'info', 'error', KEEP_SECS ) self.assertIsNone(dupe_task_obj) # Check it really didn't get re-added tasks = dm.list_objects(Task, order_field=Task.id) self.assertEqual(len(tasks), task_list_len) task = tasks[-1] self.assertEqual(task.id, task_obj.id) # Wait for task completion tm.wait_for_task(task_obj.id, 20) # We should now have no stats t_now = datetime.utcnow() sys_stats = dm.search_system_stats(t_now - timedelta(minutes=60), t_now) self.assertEqual(len(sys_stats), 0) # The completed task should only be removed after the delay we specified task = dm.get_object(Task, task_obj.id) self.assertIsNotNone(task) self.assertEqual(task.status, Task.STATUS_COMPLETE) # Wait for keep time + task server's poll time time.sleep(KEEP_SECS + 10) # Should now be gone task = dm.get_object(Task, task_obj.id) self.assertIsNone(task)
def test_task_server(self): # Create some stats t_now = datetime.utcnow() dm.save_object( SystemStats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, t_now - timedelta(minutes=5), t_now)) # We should now have stats sys_stats = dm.search_system_stats(t_now - timedelta(minutes=60), t_now) self.assertGreater(len(sys_stats), 0) # Check current task list entries tasks = dm.list_objects(Task, order_field=Task.id) task_list_len = len(tasks) # Post a background task to purge the stats KEEP_SECS = 10 task_obj = tm.add_task(None, 'Purge system statistics', 'purge_system_stats', {'before_time': t_now}, Task.PRIORITY_NORMAL, 'info', 'error', KEEP_SECS) self.assertIsNotNone(task_obj) # Check it really got added tasks = dm.list_objects(Task, order_field=Task.id) self.assertEqual(len(tasks), task_list_len + 1) task_list_len = len(tasks) # Post it again, make sure there is no dupe added dupe_task_obj = tm.add_task(None, 'Purge system statistics', 'purge_system_stats', {'before_time': t_now}, Task.PRIORITY_NORMAL, 'info', 'error', KEEP_SECS) self.assertIsNone(dupe_task_obj) # Check it really didn't get re-added tasks = dm.list_objects(Task, order_field=Task.id) self.assertEqual(len(tasks), task_list_len) task = tasks[-1] self.assertEqual(task.id, task_obj.id) # Wait for task completion tm.wait_for_task(task_obj.id, 10) # We should now have no stats t_now = datetime.utcnow() sys_stats = dm.search_system_stats(t_now - timedelta(minutes=60), t_now) self.assertEqual(len(sys_stats), 0) # The completed task should only be removed after the delay we specified task = dm.get_object(Task, task_obj.id) self.assertIsNotNone(task) self.assertEqual(task.status, Task.STATUS_COMPLETE) # Wait for keep time + task server's poll time time.sleep(KEEP_SECS + 10) # Should now be gone task = dm.get_object(Task, task_obj.id) self.assertIsNone(task)
def post(self, function_name): """ Launches a system task """ # Validate function name if getattr(tasks, function_name, None) is None: raise DoesNotExistError(function_name) # Requires super user permissions_engine.ensure_permitted(SystemPermissions.PERMIT_SUPER_USER, get_session_user()) # API parameters depend on the function params = self._get_validated_parameters(function_name, request.form) # Set remaining parameters for the task (description, task_params, priority, log_level, error_log_level, keep_secs) = self._get_task_data( function_name, params ) # Queue the task db_task = task_engine.add_task( get_session_user(), description, function_name, task_params, priority, log_level, error_log_level, keep_secs ) if db_task is None: raise AlreadyExistsError("Task is already running") # Decode the params before returning db_task.params = cPickle.loads(db_task.params) tdict = object_to_dict(db_task) if tdict.get("user") is not None: # Do not give out anything password related del tdict["user"]["password"] return make_api_success_response(tdict)
def test_task_result_none(self): # Test no return value task_obj = tm.add_task(None, 'Test return values', 'test_result_task', { 'raise_exception': False, 'return_value': None }, Task.PRIORITY_NORMAL, 'info', 'error', 5) self.assertIsNotNone(task_obj) tm.wait_for_task(task_obj.id, 10) task_obj = tm.get_task(task_obj.id, decode_attrs=True) self.assertIsNone(task_obj.result) dm.delete_object(task_obj)
def test_task_result_object(self): # Test normal return value task_obj = tm.add_task( None, 'Test return values', 'test_result_task', {'raise_exception': False, 'return_value': {'my_bool': True}}, Task.PRIORITY_NORMAL, 'info', 'error', 5 ) self.assertIsNotNone(task_obj) tm.wait_for_task(task_obj.id, 20) task_obj = tm.get_task(task_obj.id, decode_attrs=True) self.assertEqual(task_obj.result, {'my_bool': True}) dm.delete_object(task_obj)
def test_task_cancel(self): task_obj = tm.add_task(None, 'Test task cancelling', 'test_result_task', { 'raise_exception': False, 'return_value': None }, Task.PRIORITY_LOW, 'info', 'error', 0) self.assertIsNotNone(task_obj) self.assertGreater(task_obj.id, 0) # Yes, this could be a fragile test if the task server gets to it first # It has worked the first 5 times in a row I've tried it, so fingers crossed self.assertTrue(tm.cancel_task(task_obj)) task_obj = tm.get_task(task_obj.id) self.assertIsNone(task_obj)
def test_task_result_exception(self): # Test exception raised task_obj = tm.add_task( None, 'Test return values', 'test_result_task', {'raise_exception': True, 'return_value': None}, Task.PRIORITY_NORMAL, 'info', 'error', 5 ) self.assertIsNotNone(task_obj) tm.wait_for_task(task_obj.id, 20) task_obj = tm.get_task(task_obj.id, decode_attrs=True) self.assertIsInstance(task_obj.result, ValueError) self.assertEqual(repr(task_obj.result), repr(ValueError('An error happened'))) dm.delete_object(task_obj)
def put(self, folder_id): """ Moves or renames a disk folder """ params = self._get_validated_parameters(request.form) # Run this as a background task in case it takes a long time task = task_engine.add_task(get_session_user(), 'Move disk folder %d' % folder_id, 'move_folder', { 'folder_id': folder_id, 'path': params['path'] }, Task.PRIORITY_HIGH, 'info', 'error', 10) if task is None: # Task already submitted return make_api_success_response(task_accepted=True) else: return self._task_response(task, 30)
def test_task_result_exception(self): # Test exception raised task_obj = tm.add_task(None, 'Test return values', 'test_result_task', { 'raise_exception': True, 'return_value': None }, Task.PRIORITY_NORMAL, 'info', 'error', 5) self.assertIsNotNone(task_obj) tm.wait_for_task(task_obj.id, 10) task_obj = tm.get_task(task_obj.id, decode_attrs=True) self.assertIsInstance(task_obj.result, ValueError) self.assertEqual(repr(task_obj.result), repr(ValueError('An error happened'))) dm.delete_object(task_obj)
def post(self, folio_id): db_session = data_engine.db_get_session() try: # Get the portfolio folio = data_engine.get_portfolio(folio_id, _db_session=db_session) if folio is None: raise DoesNotExistError(str(folio_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_EDIT, get_session_user()) # Block the export now if it would create an empty zip file if len(folio.images) == 0: raise ParameterError( 'this portfolio is empty and cannot be published') # Create a folio-export record and start the export as a background task params = self._get_validated_object_parameters(request.form) folio_export = FolioExport(folio, params['description'], params['originals'], params['image_parameters'], params['expiry_time']) data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_PUBLISHED, folio_export.describe(True), _db_session=db_session, _commit=False) folio_export = data_engine.save_object(folio_export, refresh=True, _db_session=db_session, _commit=True) export_task = task_engine.add_task( get_session_user(), 'Export portfolio %d / export %d' % (folio.id, folio_export.id), 'export_portfolio', { 'export_id': folio_export.id, 'ignore_errors': False }, Task.PRIORITY_NORMAL, 'info', 'error', 60) # Update and return the folio-export record with the task ID folio_export.task_id = export_task.id data_engine.save_object(folio_export, _db_session=db_session, _commit=True) return make_api_success_response(object_to_dict( _prep_folioexport_object(folio, folio_export), _omit_fields + ['portfolio']), task_accepted=True) finally: db_session.close()
def delete(self, folder_id): """ Deletes a disk folder """ # v4.1 #10 delete_folder() doesn't care whether it exists, but we want the # API to return a "not found" if the folder doesn't exist on disk # (and as long as the database is already in sync with that) db_folder = data_engine.get_folder(folder_id) if db_folder is None: raise DoesNotExistError(str(folder_id)) if not path_exists(db_folder.path, require_directory=True ) and db_folder.status == Folder.STATUS_DELETED: raise DoesNotExistError(db_folder.path) # Run this as a background task in case it takes a long time task = task_engine.add_task(get_session_user(), 'Delete disk folder %d' % folder_id, 'delete_folder', {'folder_id': folder_id}, Task.PRIORITY_HIGH, 'info', 'error', 10) if task is None: # Task already submitted return make_api_success_response(task_accepted=True) else: return self._task_response(task, 30)