Coverage for portality/background.py: 95%

103 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-04 15:38 +0100

1from flask_login import login_user 

2 

3from portality.core import app 

4from portality import models 

5from portality.bll import DOAJ 

6from portality import constants 

7 

8import traceback 

9from copy import deepcopy 

10 

11 

12class BackgroundException(Exception): 

13 pass 

14 

15 

16class RetryException(Exception): 

17 pass 

18 

19 

20class BackgroundSummary(object): 

21 def __init__(self, job_id, affected=None, error=None): 

22 self.job_id = job_id 

23 self.affected = affected if affected is not None else {} 

24 self.error = error 

25 

26 def as_dict(self): 

27 return { 

28 "job_id" : self.job_id, 

29 "affected" : self.affected, 

30 "error" : self.error 

31 } 

32 

33 

34class BackgroundApi(object): 

35 """ 

36 ~~BackgroundTasks:Feature~~ 

37 """ 

38 

39 @classmethod 

40 def execute(self, background_task): 

41 # ~~->BackgroundTask:Process~~ 

42 # ~~->BackgroundJob:Model~~ 

43 job = background_task.background_job 

44 ctx = None 

45 acc = None 

46 if job.user is not None: 

47 ctx = app.test_request_context("/") 

48 ctx.push() 

49 # ~~-> Account:Model~~ 

50 acc = models.Account.pull(job.user) # FIXME: what happens when this is the "system" user 

51 if acc is not None: 

52 login_user(acc) 

53 

54 job.start() 

55 job.add_audit_message("Job Started") 

56 job.save() 

57 

58 try: 

59 background_task.run() 

60 except RetryException: 

61 if job.reference is None: 

62 job.reference = {} 

63 retries = job.reference.get("retries", 0) 

64 job.reference["retries"] = retries + 1 

65 job.save() 

66 raise 

67 except Exception as e: 

68 job.fail() 

69 job.add_audit_message("Error in Job Run") 

70 job.add_audit_message("Caught in job runner during run: " + traceback.format_exc()) 

71 job.add_audit_message("Job Run Completed") 

72 

73 job.add_audit_message("Cleanup Started") 

74 try: 

75 background_task.cleanup() 

76 except Exception as e: 

77 job.fail() 

78 job.add_audit_message("Error in Cleanup Run") 

79 job.add_audit_message("Caught in job runner during cleanup: " + traceback.format_exc()) 

80 job.add_audit_message("Job Cleanup Completed") 

81 

82 job.add_audit_message("Job Finished") 

83 if not job.is_failed(): 

84 job.success() 

85 job.save() 

86 

87 # trigger a status change event 

88 jdata = deepcopy(job.data) 

89 del jdata["audit"] 

90 eventsSvc = DOAJ.eventsService() 

91 eventsSvc.trigger(models.Event(constants.BACKGROUND_JOB_FINISHED, job.user, { 

92 "job": jdata 

93 })) 

94 

95 if ctx is not None: 

96 ctx.pop() 

97 

98 

99class BackgroundTask(object): 

100 """ 

101 All background tasks should extend from this object and override at least the following methods: 

102 

103 - run 

104 - cleanup 

105 - prepare (class method) 

106 

107 ~~BackgroundTask:Process~~ 

108 """ 

109 

110 __action__ = None 

111 """ static member variable defining the name of this task """ 

112 

113 def __init__(self, background_job): 

114 self._background_job = background_job 

115 

116 @property 

117 def background_job(self): 

118 return self._background_job 

119 

120 def run(self): 

121 """ 

122 Execute the task as specified by the background_job 

123 :return: 

124 """ 

125 raise NotImplementedError() 

126 

127 def cleanup(self): 

128 """ 

129 Cleanup after a successful OR failed run of the task 

130 :return: 

131 """ 

132 raise NotImplementedError() 

133 

134 @classmethod 

135 def prepare(cls, username, **kwargs): 

136 """ 

137 Take an arbitrary set of keyword arguments and return an instance of a BackgroundJob, 

138 or fail with a suitable exception 

139 

140 :param username: the user creating the job 

141 :param kwargs: arbitrary keyword arguments pertaining to this task type 

142 :return: a BackgroundJob instance representing this task 

143 """ 

144 raise NotImplementedError() 

145 

146 @classmethod 

147 def submit(cls, background_job): 

148 """ 

149 Submit the specified BackgroundJob to the background queue 

150 

151 :param background_job: the BackgroundJob instance 

152 :return: 

153 """ 

154 raise NotImplementedError() 

155 

156 @classmethod 

157 def get_param(cls, params, param_name, default=None): 

158 return params.get('{}__{}'.format(cls.__action__, param_name), default) 

159 

160 @classmethod 

161 def set_param(cls, params, param_name, value): 

162 params['{}__{}'.format(cls.__action__, param_name)] = value 

163 

164 @classmethod 

165 def set_reference(cls, refs, ref_name, value): 

166 refs['{}__{}'.format(cls.__action__, ref_name)] = value 

167 

168 

169class AdminBackgroundTask(BackgroundTask): 

170 """~~AdminBackgroundTask:Process->BackgroundTask:Process~~""" 

171 @classmethod 

172 def check_admin_privilege(cls, username): 

173 # ~~->Account:Model~~ 

174 a = models.Account.pull(username) 

175 if a is None: 

176 # very unlikely, as they would have had to log in to get to here.. 

177 raise BackgroundException('Admin account that is being used to prepare this job does not exist.') 

178 

179 if not a.has_role('admin'): 

180 raise BackgroundException('Account {} is not permitted to run this background task.'.format(username)) 

181 

182 @classmethod 

183 def prepare(cls, username, **kwargs): 

184 cls.check_admin_privilege(username)