Chromium Code Reviews| Index: appengine/swarming/handlers_frontend.py |
| diff --git a/appengine/swarming/handlers_frontend.py b/appengine/swarming/handlers_frontend.py |
| index 187695bedd1e85a46fd45a788ac07f3cee357c14..14713e5dd1a5a9583865370ab66658efa8d06dc7 100644 |
| --- a/appengine/swarming/handlers_frontend.py |
| +++ b/appengine/swarming/handlers_frontend.py |
| @@ -183,588 +183,82 @@ class RestrictedLaunchMapReduceJob(auth.AuthenticatingHandler): |
| class BotsListHandler(auth.AuthenticatingHandler): |
| - """Presents the list of known bots.""" |
| - ACCEPTABLE_BOTS_SORTS = { |
| - 'last_seen_ts': 'Last Seen', |
| - '-quarantined': 'Quarantined', |
| - '__key__': 'ID', |
| - } |
| - SORT_OPTIONS = [ |
| - SortOptions(k, v) for k, v in sorted(ACCEPTABLE_BOTS_SORTS.iteritems()) |
| - ] |
| + """Redirects to a list of known bots.""" |
| - @auth.autologin |
| - @auth.require(acl.is_privileged_user) |
| + @auth.public |
| def get(self): |
| limit = int(self.request.get('limit', 100)) |
| - cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor')) |
| - sort_by = self.request.get('sort_by', '__key__') |
| - if sort_by not in self.ACCEPTABLE_BOTS_SORTS: |
| - self.abort(400, 'Invalid sort_by query parameter') |
| - |
| - if sort_by[0] == '-': |
| - order = datastore_query.PropertyOrder( |
| - sort_by[1:], datastore_query.PropertyOrder.DESCENDING) |
| - else: |
| - order = datastore_query.PropertyOrder( |
| - sort_by, datastore_query.PropertyOrder.ASCENDING) |
| dimensions = ( |
| l.strip() for l in self.request.get('dimensions', '').splitlines() |
| ) |
| dimensions = [i for i in dimensions if i] |
| - now = utils.utcnow() |
| - cutoff = now - datetime.timedelta( |
| - seconds=config.settings().bot_death_timeout_secs) |
| - |
| - # TODO(maruel): Counting becomes an issue at the 10k range, at that point it |
| - # should be prepopulated in an entity and updated via a cron job. |
| - num_bots_busy_future = bot_management.BotInfo.query( |
| - bot_management.BotInfo.is_busy == True).count_async() |
| - num_bots_dead_future = bot_management.BotInfo.query( |
| - bot_management.BotInfo.last_seen_ts < cutoff).count_async() |
| - num_bots_quarantined_future = bot_management.BotInfo.query( |
| - bot_management.BotInfo.quarantined == True).count_async() |
| - num_bots_total_future = bot_management.BotInfo.query().count_async() |
| - q = bot_management.BotInfo.query().order(order) |
| - for d in dimensions: |
| - q = q.filter(bot_management.BotInfo.dimensions_flat == d) |
| - fetch_future = q.fetch_page_async(limit, start_cursor=cursor) |
| - |
| - # TODO(maruel): self.request.host_url should be the default AppEngine url |
| - # version and not the current one. It is only an issue when |
| - # version-dot-appid.appspot.com urls are used to access this page. |
| - # TODO(aludwin): Display both gRPC and non-gRPC versions |
| - version = bot_code.get_bot_version(self.request.host_url) |
| - bots, cursor, more = fetch_future.get_result() |
| - # Prefetch the tasks. We don't actually use the value here, it'll be |
| - # implicitly used by ndb local's cache when refetched by the html template. |
| - tasks = filter(None, (b.task for b in bots)) |
| - ndb.get_multi(tasks) |
| - num_bots_busy = num_bots_busy_future.get_result() |
| - num_bots_dead = num_bots_dead_future.get_result() |
| - num_bots_quarantined = num_bots_quarantined_future.get_result() |
| - num_bots_total = num_bots_total_future.get_result() |
| - try_link = '/botlist?l=%d' % limit |
| + new_ui_link = '/botlist?l=%d' % limit |
| if dimensions: |
| - try_link += '&f=' + '&f='.join(dimensions) |
| - params = { |
| - 'bots': bots, |
| - 'current_version': version, |
| - 'cursor': cursor.urlsafe() if cursor and more else '', |
| - 'dimensions': '\n'.join(dimensions), |
| - 'is_admin': acl.is_admin(), |
| - 'is_privileged_user': acl.is_privileged_user(), |
| - 'limit': limit, |
| - 'now': now, |
| - 'num_bots_alive': num_bots_total - num_bots_dead, |
| - 'num_bots_busy': num_bots_busy, |
| - 'num_bots_dead': num_bots_dead, |
| - 'num_bots_quarantined': num_bots_quarantined, |
| - 'try_link': try_link, |
| - 'sort_by': sort_by, |
| - 'sort_options': self.SORT_OPTIONS, |
| - 'xsrf_token': self.generate_xsrf_token(), |
| - } |
| - self.response.write( |
| - template.render('swarming/restricted_botslist.html', params)) |
| + new_ui_link += '&f=' + '&f='.join(dimensions) |
| + |
| + self.redirect(new_ui_link) |
| class BotHandler(auth.AuthenticatingHandler): |
| - """Returns data about the bot, including last tasks and events.""" |
| + """Redirects to a page about the bot, including last tasks and events.""" |
| - @auth.autologin |
| - @auth.require(acl.is_privileged_user) |
| + @auth.public |
| def get(self, bot_id): |
| - # pagination is currently for tasks, not events. |
| - limit = int(self.request.get('limit', 100)) |
| - cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor')) |
| - run_results_future = task_result.TaskRunResult.query( |
| - task_result.TaskRunResult.bot_id == bot_id).order( |
| - -task_result.TaskRunResult.started_ts).fetch_page_async( |
| - limit, start_cursor=cursor) |
| - bot_future = bot_management.get_info_key(bot_id).get_async() |
| - events_future = bot_management.get_events_query( |
| - bot_id, True).fetch_async(100) |
| - |
| - now = utils.utcnow() |
| - |
| - # Calculate the time this bot was idle. |
| - idle_time = datetime.timedelta() |
| - run_time = datetime.timedelta() |
| - run_results, cursor, more = run_results_future.get_result() |
| - if run_results: |
| - run_time = run_results[0].duration_now(now) or datetime.timedelta() |
| - if not cursor and run_results[0].state != task_result.State.RUNNING: |
| - # Add idle time since last task completed. Do not do this when a cursor |
| - # is used since it's not representative. |
| - idle_time = now - run_results[0].ended_ts |
| - for index in xrange(1, len(run_results)): |
| - # .started_ts will always be set by definition but .ended_ts may be None |
| - # if the task was abandoned. We can't count idle time since the bot may |
| - # have been busy running *another task*. |
| - # TODO(maruel): One option is to add a third value "broken_time". |
| - # Looking at timestamps specifically could help too, e.g. comparing |
| - # ended_ts of this task vs the next one to see if the bot was assigned |
| - # two tasks simultaneously. |
| - if run_results[index].ended_ts: |
| - idle_time += ( |
| - run_results[index-1].started_ts - run_results[index].ended_ts) |
| - # We are taking the whole time the bot was doing work, not just the |
| - # duration associated with the task. |
| - duration = run_results[index].duration_as_seen_by_server |
| - if duration: |
| - run_time += duration |
| - |
| - events = events_future.get_result() |
| - bot = bot_future.get_result() |
| - if not bot and events: |
| - # If there is not BotInfo, look if there are BotEvent child of this |
| - # entity. If this is the case, it means the bot was deleted but it's |
| - # useful to show information about it to the user even if the bot was |
| - # deleted. For example, it could be an auto-scaled bot. |
| - bot = bot_management.BotInfo( |
| - key=bot_management.get_info_key(bot_id), |
| - dimensions_flat=bot_management.dimensions_to_flat( |
| - events[0].dimensions), |
| - state=events[0].state, |
| - external_ip=events[0].external_ip, |
| - authenticated_as=events[0].authenticated_as, |
| - version=events[0].version, |
| - quarantined=events[0].quarantined, |
| - task_id=events[0].task_id, |
| - last_seen_ts=events[0].ts) |
| - |
| - params = { |
| - 'bot': bot, |
| - 'bot_id': bot_id, |
| - # TODO(aludwin): Use the bot's correct gRPC status to determine the |
| - # version |
| - 'current_version': bot_code.get_bot_version(self.request.host_url), |
| - 'cursor': cursor.urlsafe() if cursor and more else None, |
| - 'events': events, |
| - 'idle_time': idle_time, |
| - 'is_admin': acl.is_admin(), |
| - 'limit': limit, |
| - 'now': now, |
| - 'run_results': run_results, |
| - 'run_time': run_time, |
| - 'try_link': '/bot?id=%s' % bot_id, |
| - 'xsrf_token': self.generate_xsrf_token(), |
| - } |
| - self.response.write( |
| - template.render('swarming/restricted_bot.html', params)) |
| + self.redirect('/bot?id=%s' % bot_id) |
| class BotDeleteHandler(auth.AuthenticatingHandler): |
|
M-A Ruel
2016/11/11 16:19:18
All POST handlers are not needed anymore, remove.
|
| - """Deletes a known bot. |
| + """Redirects to a page about the bot, where the delete can be called.""" |
| - This only deletes the BotInfo, not BotRoot, BotEvent's nor BotSettings. |
| - |
| - This is sufficient so the bot doesn't show up on the Bots page while keeping |
| - historical data. |
| - """ |
| - |
| - @auth.require(acl.is_admin) |
| + @auth.public |
| def post(self, bot_id): |
| - bot_key = bot_management.get_info_key(bot_id) |
| - if bot_key.get(): |
| - bot_key.delete() |
| - self.redirect('/restricted/bots') |
| + self.redirect('/bot?id=%s' % bot_id) |
| ### User accessible pages. |
| class TasksHandler(auth.AuthenticatingHandler): |
| - """Lists all requests and allows callers to manage them.""" |
| - # Each entry is an item in the Sort column. |
| - # Each entry is (key, text, hover) |
| - SORT_CHOICES = [ |
| - ('created_ts', 'Created', 'Most recently created tasks are shown first.'), |
| - ('modified_ts', 'Active', |
| - 'Shows the most recently active tasks first. Using this order resets ' |
| - 'state to \'All\'.'), |
| - ('completed_ts', 'Completed', |
| - 'Shows the most recently completed tasks first. Using this order resets ' |
| - 'state to \'All\'.'), |
| - ('abandoned_ts', 'Abandoned', |
| - 'Shows the most recently abandoned tasks first. Using this order resets ' |
| - 'state to \'All\'.'), |
| - ] |
| + """Redirects to a list of all task requests.""" |
| - # Each list is one column in the Task state filtering column. |
| - # Each sublist is the checkbox item in this column. |
| - # Each entry is (key, text, hover) |
| - # TODO(maruel): Evaluate what the categories the users would like for |
| - # diagnosis, then adapt the DB to enable efficient queries. |
| - STATE_CHOICES = [ |
| - [ |
| - ('all', 'All', 'All tasks ever requested independent of their state.'), |
| - ('pending', 'Pending', |
| - 'Tasks that are still ready to be assigned to a bot. Using this order ' |
| - 'resets order to \'Created\'.'), |
| - ('running', 'Running', |
| - 'Tasks being currently executed by a bot. Using this order resets ' |
| - 'order to \'Created\'.'), |
| - ('pending_running', 'Pending|running', |
| - 'Tasks either \'pending\' or \'running\'. Using this order resets ' |
| - 'order to \'Created\'.'), |
| - ], |
| - [ |
| - ('completed', 'Completed', |
| - 'All tasks that are completed, independent if the task itself ' |
| - 'succeeded or failed. This excludes tasks that had an infrastructure ' |
| - 'failure. Using this order resets order to \'Created\'.'), |
| - ('completed_success', 'Successes', |
| - 'Tasks that completed successfully. Using this order resets order to ' |
| - '\'Created\'.'), |
| - ('completed_failure', 'Failures', |
| - 'Tasks that were executed successfully but failed, e.g. exit code is ' |
| - 'non-zero. Using this order resets order to \'Created\'.'), |
| - ('timed_out', 'Timed out', |
| - 'The execution timed out, so it was forcibly killed.'), |
| - ], |
| - [ |
| - ('bot_died', 'Bot died', |
| - 'The bot stopped sending updates while running the task, causing the ' |
| - 'task execution to time out. This is considered an infrastructure ' |
| - 'failure and the usual reason is that the bot BSOD\'ed or ' |
| - 'spontaneously rebooted. Using this order resets order to ' |
| - '\'Created\'.'), |
| - ('expired', 'Expired', |
| - 'The task was not assigned a bot until its expiration timeout, causing ' |
| - 'the task to never being assigned to a bot. This can happen when the ' |
| - 'dimension filter was not available or overloaded with a low priority. ' |
| - 'Either fix the priority or bring up more bots with these dimensions. ' |
| - 'Using this order resets order to \'Created\'.'), |
| - ('canceled', 'Canceled', |
| - 'The task was explictly canceled by a user before it started ' |
| - 'executing. Using this order resets order to \'Created\'.'), |
| - ], |
| - ] |
| - |
| - @auth.autologin |
| - @auth.require(acl.is_user) |
| + @auth.public |
| def get(self): |
| - cursor_str = self.request.get('cursor') |
| limit = int(self.request.get('limit', 100)) |
| - sort = self.request.get('sort', self.SORT_CHOICES[0][0]) |
| - state = self.request.get('state', self.STATE_CHOICES[0][0][0]) |
| - counts = self.request.get('counts', '').strip() |
| task_tags = [ |
| line for line in self.request.get('task_tag', '').splitlines() if line |
| ] |
| - if not any(sort == i[0] for i in self.SORT_CHOICES): |
| - self.abort(400, 'Invalid sort') |
| - if not any(any(state == i[0] for i in j) for j in self.STATE_CHOICES): |
| - self.abort(400, 'Invalid state') |
| - |
| - if sort != 'created_ts': |
| - # Zap all filters in this case to reduce the number of required indexes. |
| - # Revisit according to the user requests. |
| - state = 'all' |
| - |
| - now = utils.utcnow() |
| - # "Temporarily" disable the count. This is too slow on the prod server |
| - # (>10s). The fix is to have the web page do a XHR query to get the values |
| - # asynchronously. |
| - counts_future = None |
| - if counts == 'true': |
| - counts_future = self._get_counts_future(now) |
| - |
| - try: |
| - if task_tags: |
| - # Enforce created_ts when tags are used. |
| - sort = 'created_ts' |
| - query = task_result.get_result_summaries_query( |
| - None, None, sort, state, task_tags) |
| - tasks, cursor_str = datastore_utils.fetch_page(query, limit, cursor_str) |
| - |
| - # Prefetch the TaskRequest all at once, so that ndb's in-process cache has |
| - # it instead of fetching them one at a time indirectly when using |
| - # TaskResultSummary.request_key.get(). |
| - futures = ndb.get_multi_async(t.request_key for t in tasks) |
| - |
| - # Evaluate the counts to print the filtering columns with the associated |
| - # numbers. |
| - state_choices = self._get_state_choices(counts_future) |
| - except ValueError as e: |
| - self.abort(400, str(e)) |
| - |
| - def safe_sum(items): |
| - return sum(items, datetime.timedelta()) |
| - |
| - def avg(items): |
| - if not items: |
| - return 0. |
| - return safe_sum(items) / len(items) |
| - |
| - def median(items): |
| - if not items: |
| - return 0. |
| - middle = len(items) / 2 |
| - if len(items) % 2: |
| - return items[middle] |
| - return (items[middle-1]+items[middle]) / 2 |
| - |
| - gen = (t.duration_now(now) for t in tasks) |
| - durations = sorted(t for t in gen if t is not None) |
| - gen = (t.pending_now(now) for t in tasks) |
| - pendings = sorted(t for t in gen if t is not None) |
| - total_cost_usd = sum(t.cost_usd for t in tasks) |
| - total_cost_saved_usd = sum( |
| - t.cost_saved_usd for t in tasks if t.cost_saved_usd) |
| - # Include the overhead in the total amount of time saved, since it's |
| - # overhead saved. |
| - # In theory, t.duration_as_seen_by_server should always be set when |
| - # t.deduped_from is set but there has some broken entities in the datastore. |
| - total_saved = safe_sum( |
| - t.duration_as_seen_by_server for t in tasks |
| - if t.deduped_from and t.duration_as_seen_by_server) |
| - duration_sum = safe_sum(durations) |
| - total_saved_percent = ( |
| - (100. * total_saved.total_seconds() / duration_sum.total_seconds()) |
| - if duration_sum else 0.) |
| - |
| - try_link = '/tasklist?l=%d' % limit |
| + new_ui_link = '/tasklist?l=%d' % limit |
| if task_tags: |
| - try_link += '&f=' + '&f='.join(task_tags) |
| - params = { |
| - 'cursor': cursor_str, |
| - 'duration_average': avg(durations), |
| - 'duration_median': median(durations), |
| - 'duration_sum': duration_sum, |
| - 'has_pending': any(t.is_pending for t in tasks), |
| - 'has_running': any(t.is_running for t in tasks), |
| - 'is_admin': acl.is_admin(), |
| - 'is_privileged_user': acl.is_privileged_user(), |
| - 'limit': limit, |
| - 'now': now, |
| - 'pending_average': avg(pendings), |
| - 'pending_median': median(pendings), |
| - 'pending_sum': safe_sum(pendings), |
| - 'show_footer': bool(pendings or durations), |
| - 'sort': sort, |
| - 'sort_choices': self.SORT_CHOICES, |
| - 'state': state, |
| - 'state_choices': state_choices, |
| - 'task_tag': '\n'.join(task_tags), |
| - 'tasks': tasks, |
| - 'total_cost_usd': total_cost_usd, |
| - 'total_cost_saved_usd': total_cost_saved_usd, |
| - 'total_saved': total_saved, |
| - 'total_saved_percent': total_saved_percent, |
| - 'try_link': try_link, |
| - 'xsrf_token': self.generate_xsrf_token(), |
| - } |
| - # TODO(maruel): If admin or if the user is task's .user, show the Cancel |
| - # button. Do not show otherwise. |
| - self.response.write(template.render('swarming/user_tasks.html', params)) |
| + new_ui_link += '&f=' + '&f='.join(task_tags) |
| - # Do not let dangling futures linger around. |
| - ndb.Future.wait_all(futures) |
| + self.redirect(new_ui_link) |
| - def _get_counts_future(self, now): |
| - """Returns all the counting futures in parallel.""" |
| - counts_future = {} |
| - last_24h = now - datetime.timedelta(days=1) |
| - for state_key, _, _ in itertools.chain.from_iterable(self.STATE_CHOICES): |
| - query = task_result.get_result_summaries_query( |
| - last_24h, None, 'created_ts', state_key, None) |
| - counts_future[state_key] = query.count_async() |
| - return counts_future |
| - def _get_state_choices(self, counts_future): |
| - """Converts STATE_CHOICES with _get_counts_future() into nice text.""" |
| - # Appends the number of tasks for each filter. It gives a sense of how much |
| - # things are going on. |
| - if counts_future: |
| - counts = {k: v.get_result() for k, v in counts_future.iteritems()} |
| - state_choices = [] |
| - for choice_list in self.STATE_CHOICES: |
| - state_choices.append([]) |
| - for state_key, name, title in choice_list: |
| - if counts_future: |
| - name += ' (%d)' % counts[state_key] |
| - state_choices[-1].append((state_key, name, title)) |
| - return state_choices |
| +class TaskHandler(auth.AuthenticatingHandler): |
| + """Redirects to a page containing task request and result.""" |
| - |
| -class BaseTaskHandler(auth.AuthenticatingHandler): |
| - """Handler that acts on a single task. |
| - |
| - Ensures that the user has access to the task. |
| - """ |
| - def get_request_and_result(self, task_id, with_secret_bytes=False): |
| - """Retrieves the TaskRequest for 'task_id' and enforces the ACL. |
| - |
| - Supports both TaskResultSummary (ends with 0) or TaskRunResult (ends with 1 |
| - or 2). |
| - |
| - Returns: |
| - tuple(TaskRequest, SecretBytes, result): result can be either for |
| - a TaskRunResult or a TaskResultSummay. |
| - """ |
| - try: |
| - key = task_pack.unpack_result_summary_key(task_id) |
| - request_key = task_pack.result_summary_key_to_request_key(key) |
| - except ValueError: |
| - try: |
| - key = task_pack.unpack_run_result_key(task_id) |
| - request_key = task_pack.result_summary_key_to_request_key( |
| - task_pack.run_result_key_to_result_summary_key(key)) |
| - except ValueError: |
| - self.abort(404, 'Invalid key format.') |
| - if with_secret_bytes: |
| - sb_key = task_pack.request_key_to_secret_bytes_key(request_key) |
| - request, result, secret_bytes = ndb.get_multi((request_key, key, sb_key)) |
| - else: |
| - request, result = ndb.get_multi((request_key, key)) |
| - secret_bytes = None |
| - if not request or not result: |
| - self.abort(404, '%s not found.' % key.id()) |
| - if not request.has_access: |
| - self.abort(403, '%s is not accessible.' % key.id()) |
| - return request, secret_bytes, result |
| - |
| - |
| -class TaskHandler(BaseTaskHandler): |
| - """Show the full text of a task request and its result.""" |
| - |
| - @staticmethod |
| - def packages_grouped_by_path(flat_packages): |
| - """Returns sorted [(path, [PinInfo, ...])]. |
| - |
| - Used by user_task.html. |
| - """ |
| - retval = collections.defaultdict(list) |
| - for pkg in flat_packages: |
| - retval[pkg.path].append(pkg) |
| - return sorted(retval.iteritems()) |
| - |
| - @auth.autologin |
| - @auth.require(acl.is_user) |
| + @auth.public |
| def get(self, task_id): |
| - request, _, result = self.get_request_and_result(task_id) |
| - parent_task_future = None |
| - if request.parent_task_id: |
| - parent_key = task_pack.unpack_run_result_key(request.parent_task_id) |
| - parent_task_future = parent_key.get_async() |
| - children_tasks_futures = [ |
| - task_pack.unpack_result_summary_key(c).get_async() |
| - for c in result.children_task_ids |
| - ] |
| + self.redirect('/task?id=%s' % task_id) |
| - bot_id = result.bot_id |
| - following_task_future = None |
| - previous_task_future = None |
| - if result.started_ts: |
| - # Use a shortcut name because it becomes unwieldy otherwise. |
| - cls = task_result.TaskRunResult |
| - # Note that the links will be to the TaskRunResult, not to |
| - # TaskResultSummary. |
| - following_task_future = cls.query( |
| - cls.bot_id == bot_id, |
| - cls.started_ts > result.started_ts, |
| - ).order(cls.started_ts).get_async() |
| - previous_task_future = cls.query( |
| - cls.bot_id == bot_id, |
| - cls.started_ts < result.started_ts, |
| - ).order(-cls.started_ts).get_async() |
| +class TaskCancelHandler(auth.AuthenticatingHandler): |
|
M-A Ruel
2016/11/11 16:19:18
Remove
|
| + """Redirects to a page about the task, where the cancel can be called.""" |
| - bot_future = ( |
| - bot_management.get_info_key(bot_id).get_async() if bot_id else None) |
| - |
| - following_task = None |
| - if following_task_future: |
| - following_task = following_task_future.get_result() |
| - |
| - previous_task = None |
| - if previous_task_future: |
| - previous_task = previous_task_future.get_result() |
| - |
| - parent_task = None |
| - if parent_task_future: |
| - parent_task = parent_task_future.get_result() |
| - children_tasks = [c.get_result() for c in children_tasks_futures] |
| - |
| - cipd = None |
| - if request.properties.cipd_input: |
| - cipd = { |
| - 'server': request.properties.cipd_input.server, |
| - 'client_package': request.properties.cipd_input.client_package, |
| - 'packages': self.packages_grouped_by_path( |
| - request.properties.cipd_input.packages), |
| - } |
| - |
| - cipd_pins = None |
| - if result.cipd_pins: |
| - cipd_pins = { |
| - 'client_package': result.cipd_pins.client_package, |
| - 'packages': self.packages_grouped_by_path(result.cipd_pins.packages), |
| - } |
| - |
| - params = { |
| - 'bot': bot_future.get_result() if bot_future else None, |
| - 'children_tasks': children_tasks, |
| - 'cipd': cipd, |
| - 'cipd_pins': cipd_pins, |
| - 'is_admin': acl.is_admin(), |
| - 'is_gae_admin': users.is_current_user_admin(), |
| - 'is_privileged_user': acl.is_privileged_user(), |
| - 'following_task': following_task, |
| - 'full_appid': os.environ['APPLICATION_ID'], |
| - 'host_url': self.request.host_url, |
| - 'is_running': result.state == task_result.State.RUNNING, |
| - 'parent_task': parent_task, |
| - 'previous_task': previous_task, |
| - 'request': request, |
| - 'task': result, |
| - 'try_link': '/task?id=%s' % task_id, |
| - 'xsrf_token': self.generate_xsrf_token(), |
| - } |
| - self.response.write(template.render('swarming/user_task.html', params)) |
| - |
| - |
| -class TaskCancelHandler(BaseTaskHandler): |
| - """Cancel a task.""" |
| - |
| - @auth.require(acl.is_user) |
| + @auth.public |
| def post(self, task_id): |
| - request, _, result = self.get_request_and_result(task_id) |
| - if not task_scheduler.cancel_task(request, result.key)[0]: |
| - self.abort(400, 'Task cancelation error') |
| - # The cancel button appears at both the /tasks and /task pages. Redirect to |
| - # the right place. |
| - if self.request.get('redirect_to', '') == 'listing': |
| - self.redirect('/user/tasks') |
| - else: |
| - self.redirect('/user/task/%s' % task_id) |
| - |
| + self.redirect('/task?id=%s' % task_id) |
| -class TaskRetryHandler(BaseTaskHandler): |
| - """Retries the same task but with new metadata. |
| - Retrying a task forcibly make it not idempotent so the task is unconditionally |
| - scheduled. |
| - """ |
| +class TaskRetryHandler(auth.AuthenticatingHandler): |
|
M-A Ruel
2016/11/11 16:19:18
Remove
|
| + """Redirects to a page about the task, where the cancel can be called.""" |
| - @auth.require(acl.is_user) |
| + @auth.public |
| def post(self, task_id): |
| - original_request, secret_bytes, _ = self.get_request_and_result( |
| - task_id, with_secret_bytes=True) |
| - # Retrying a task is essentially reusing the same task request as the |
| - # original one, but with new parameters. |
| - new_request = task_request.new_request_clone( |
| - original_request, secret_bytes, |
| - allow_high_priority=acl.can_schedule_high_priority_tasks()) |
| - result_summary = task_scheduler.schedule_request( |
| - new_request, secret_bytes) |
| - self.redirect('/user/task/%s' % result_summary.task_id) |
| + self.redirect('/task?id=%s' % task_id) |
| ### Public pages. |