def to_json(obj): if isinstance(obj, datetime): return format_datetime(obj) raise TypeError('Cannot JSON-encode object')
def job(self, id_, command=None, finishid=None, barerows=None, unfiltered=None, limit=None, enddate=None, submit_config=None, submit_relink=None, submit_confirm=None, submit_cancel=None, orphan=None, graceperiod=None, timeout=None, success_pattern=None, warning_pattern=None, fail_pattern=None, note=None, inhibit=None, crabid=None, submit_notify=None, **kwargs): """Displays information about a current job. Currently also supports showing the job output. If command='output' but the finishid is not provided, then it will find the most recent output for the given job.""" try: id_ = int(id_) except ValueError: raise HTTPError(400, 'Job number not a number') info = self.store.get_job_info(id_) if info is None: raise HTTPError(404, 'Job not found') if command is None: if limit is None: limit = 100 else: try: limit = int(limit) except ValueError: raise HTTPError(400, 'Limit is not a number') if limit < 1: raise HTTPError(400, 'Limit should not be less than one') elif limit > 1000: raise HTTPError(400, 'Limit greater than a thousand') if unfiltered is None: squash_start = True else: squash_start = False if enddate is not None: try: enddate = parse_datetime(enddate) except ValueError: raise HTTPError(400, 'Start date format is invalid') events = self.store.get_job_events(id_, limit, end=enddate) if events: lastdatetime = format_datetime(events[-1]['datetime']) else: lastdatetime = None # Filter the events. filter = CrabEventFilter(self.store, info['timezone']) events = filter(events, squash_start=squash_start, skip_trivial=squash_start) if barerows is not None: return self._write_template( 'jobevents.html', {'id': id_, 'events': events, 'lastdatetime': lastdatetime}) # Try to convert the times to the timezone shown on the page. info['installed'] = filter.in_timezone(info['installed']) info['deleted'] = filter.in_timezone(info['deleted']) # Fetch configuration. config = self.store.get_job_config(id_) # Fetch job notifications. if config is not None: notification = self.store.get_job_notifications( config['configid']) else: notification = None return self._write_template( 'job.html', {'id': id_, 'info': info, 'config': config, 'status': self.monitor.get_job_status(id_), 'notification': notification, 'events': events, 'lastdatetime': lastdatetime}) elif command == 'clear': if submit_confirm: self.store.log_alarm(id_, CrabStatus.CLEARED) # Wait for the monitor to process new events so that # that the job is in an 'OK' state before we reload the page. with self.monitor.new_event: self.monitor.new_event.wait(10) raise HTTPRedirect("/job/" + str(id_)) elif submit_cancel: raise HTTPRedirect("/job/" + str(id_)) else: return self._write_template( 'confirm.html', {'id': id_, 'info': info, 'title': 'clear status', 'description': 'Reset the job status?', 'target': '/job/' + str(id_) + '/clear'}) elif command == 'uninhibit': if submit_confirm: self.store.disable_inhibit(id_) raise HTTPRedirect('/job/' + str(id_)) elif submit_cancel: raise HTTPRedirect('/job/' + str(id_)) else: return self._write_template( 'confirm.html', {'id': id_, 'info': info, 'title': 'resume', 'description': 'Resume inhibited job?', 'target': '/job/' + str(id_) + '/uninhibit'}) elif command == 'delete': notdeleted = info['deleted'] is None if submit_confirm: if notdeleted: self.store.delete_job(id_) else: self.store.undelete_job(id_) raise HTTPRedirect('/job/' + str(id_)) elif submit_cancel: raise HTTPRedirect('/job/' + str(id_)) else: return self._write_template( 'confirm.html', {'id': id_, 'info': info, 'title': 'delete' if notdeleted else 'undelete', 'description': (('Delete' if notdeleted else 'Undelete') + ' this job from the server?'), 'target': '/job/' + str(id_) + '/delete'}) elif command == 'changeid': if submit_confirm: if crabid != '': if self.store.get_jobs( info['host'], info['user'], include_deleted=True, crabid=crabid): raise HTTPError( 400, 'Specified job ID already exists.') else: self.store.update_job(id_, crabid=crabid) raise HTTPRedirect('/job/' + str(id_)) else: raise HTTPError(400, 'Specified job ID is blank.') elif submit_cancel: raise HTTPRedirect('/job/' + str(id_)) else: return self._write_template( 'confirm.html', {'id': id_, 'info': info, 'title': 'change identifier', 'description': 'Change Job ID for this job? Please note ' 'that the ID which will be used to report ' 'events related to this job should be ' 'updated at the same time to ensure that ' 'the job continues to be correctly ' 'identified. This should be done in the ' 'crontab if the CRABID variable is used, ' 'or in the cron job itself in the case ' 'of Crab-aware cron jobs.', 'target': '/job/' + str(id_) + '/changeid', 'data': {'crabid': crabid}}) elif command == 'output': finishid_next = None finishid_prev = None if finishid is None: # If finishid is not specified, select the most recent # for this job. finishes = self.store.get_job_finishes(id_, limit=2) if not finishes: raise HTTPError(404, 'No job output found') finish = finishes[0] finishid = finish['finishid'] if len(finishes) > 1: finishid_prev = finishes[1]['finishid'] else: try: finishid = int(finishid) except ValueError: raise HTTPError(400, 'Finish ID is not a number') finishes = self.store.get_job_finishes(id_, finishid=finishid) if not finishes: raise HTTPError(404, 'Finish ID not found or wrong job') finish = finishes[0] finishes = self.store.get_job_finishes(id_, 1, before=finishid) if finishes: finishid_prev = finishes[0]['finishid'] finishes = self.store.get_job_finishes(id_, 1, after=finishid) if finishes: finishid_next = finishes[0]['finishid'] (stdout, stderr) = self.store.get_job_output( finishid, info['host'], info['user'], id_, info['crabid']) filter = CrabEventFilter(self.store, info['timezone']) finish['datetime'] = filter.in_timezone(finish['datetime']) return self._write_template( 'joboutput.html', {'id': id_, 'info': info, 'finish': finish, 'stdout': stdout, 'stderr': stderr, 'next': finishid_next, 'prev': finishid_prev}) elif command == 'config': if submit_relink: try: orphan = int(orphan) except ValueError: raise HTTPError(400, 'Orphan number not a number') self.store.relink_job_config(orphan, id_) raise HTTPRedirect("/job/" + str(id_)) elif submit_config: try: if timeout == '': timeout = None elif timeout is not None: timeout = int(timeout) if graceperiod == '': graceperiod = None elif graceperiod is not None: graceperiod = int(graceperiod) if success_pattern == '': success_pattern = None if warning_pattern == '': warning_pattern = None if fail_pattern == '': fail_pattern = None if note is not None: note = note.strip() if note == '': note = None inhibit = inhibit is not None except ValueError: raise HTTPError(400, 'Time not a number') self.store.write_job_config( id_, graceperiod, timeout, success_pattern, warning_pattern, fail_pattern, note, inhibit) raise HTTPRedirect("/job/" + str(id_)) else: config = self.store.get_job_config(id_) if config is None: orphan = self.store.get_orphan_configs() else: orphan = None return self._write_template( 'jobconfig.html', {'id': id_, 'info': info, 'config': config, 'orphan': orphan}) elif command == 'notify': if submit_notify: # Ensure that this job has a configuration entry # so that we can link to it. config = self.store.get_job_config(id_) if config is not None: configid = config['configid'] else: configid = self.store.write_job_config(id_) # Make a list of notifications for this job # so that we can delete those which are not # included in the POST parameters. existing = set() for notification in self.store.get_job_notifications(configid): existing.add(notification['notifyid']) # Update existing notifications. for kwarg in kwargs: match = re.search('method_(new_)?(\d+)', kwarg) if not match: continue if match.group(1): key = ''.join(match.groups()) notifyid = None else: key = match.group(2) notifyid = int(key) existing.discard(notifyid) self.store.write_notification( notifyid, configid, None, None, kwargs['method_' + key], kwargs['address_' + key], empty_to_none(kwargs['time_' + key]), empty_to_none(kwargs['timezone_' + key]), 'include_ok_' + key not in kwargs, 'include_warning_' + key not in kwargs, 'include_error_' + key not in kwargs, 'include_output_' + key in kwargs) # Delete existing notifications which were not present. for notifyid in existing: self.store.delete_notification(notifyid) raise HTTPRedirect('/job/' + str(id_)) else: config = self.store.get_job_config(id_) if config is not None: notifications = self.store.get_job_notifications( config['configid']) else: notifications = None return self._write_template( 'editnotify.html', {'match_mode': False, 'id': id_, 'info': info, 'notifications': notifications}) else: raise HTTPError(404, 'Unknown job command')