Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: appengine/swarming/handlers_frontend.py

Issue 2523433002: Add link to old ui back in (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | appengine/swarming/templates/bot_view.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2013 The LUCI Authors. All rights reserved. 1 # Copyright 2013 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """Main entry point for Swarming service. 5 """Main entry point for Swarming service.
6 6
7 This file contains the URL handlers for all the Swarming service URLs, 7 This file contains the URL handlers for all the Swarming service URLs,
8 implemented using the webapp2 framework. 8 implemented using the webapp2 framework.
9 """ 9 """
10 10
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after
175 # New tasks should show up on the status page. 175 # New tasks should show up on the status page.
176 if success: 176 if success:
177 self.redirect('/restricted/mapreduce/status') 177 self.redirect('/restricted/mapreduce/status')
178 else: 178 else:
179 self.abort(500, 'Failed to launch the job') 179 self.abort(500, 'Failed to launch the job')
180 180
181 181
182 ### acl.is_privileged_user pages. 182 ### acl.is_privileged_user pages.
183 183
184 184
185 class BotsListHandler(auth.AuthenticatingHandler): 185 class OldBotsListHandler(auth.AuthenticatingHandler):
186 """Redirects to a list of known bots.""" 186 """Presents the list of known bots."""
187 187 ACCEPTABLE_BOTS_SORTS = {
188 @auth.public 188 'last_seen_ts': 'Last Seen',
189 '-quarantined': 'Quarantined',
190 '__key__': 'ID',
191 }
192 SORT_OPTIONS = [
193 SortOptions(k, v) for k, v in sorted(ACCEPTABLE_BOTS_SORTS.iteritems())
194 ]
195
196 @auth.autologin
197 @auth.require(acl.is_privileged_user)
189 def get(self): 198 def get(self):
190 limit = int(self.request.get('limit', 100)) 199 limit = int(self.request.get('limit', 100))
200 cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor'))
201 sort_by = self.request.get('sort_by', '__key__')
202 if sort_by not in self.ACCEPTABLE_BOTS_SORTS:
203 self.abort(400, 'Invalid sort_by query parameter')
204
205 if sort_by[0] == '-':
206 order = datastore_query.PropertyOrder(
207 sort_by[1:], datastore_query.PropertyOrder.DESCENDING)
208 else:
209 order = datastore_query.PropertyOrder(
210 sort_by, datastore_query.PropertyOrder.ASCENDING)
191 211
192 dimensions = ( 212 dimensions = (
193 l.strip() for l in self.request.get('dimensions', '').splitlines() 213 l.strip() for l in self.request.get('dimensions', '').splitlines()
194 ) 214 )
195 dimensions = [i for i in dimensions if i] 215 dimensions = [i for i in dimensions if i]
196 216
197 new_ui_link = '/botlist?l=%d' % limit 217 now = utils.utcnow()
218 cutoff = now - datetime.timedelta(
219 seconds=config.settings().bot_death_timeout_secs)
220
221 # TODO(maruel): Counting becomes an issue at the 10k range, at that point it
222 # should be prepopulated in an entity and updated via a cron job.
223 num_bots_busy_future = bot_management.BotInfo.query(
224 bot_management.BotInfo.is_busy == True).count_async()
225 num_bots_dead_future = bot_management.BotInfo.query(
226 bot_management.BotInfo.last_seen_ts < cutoff).count_async()
227 num_bots_quarantined_future = bot_management.BotInfo.query(
228 bot_management.BotInfo.quarantined == True).count_async()
229 num_bots_total_future = bot_management.BotInfo.query().count_async()
230 q = bot_management.BotInfo.query().order(order)
231 for d in dimensions:
232 q = q.filter(bot_management.BotInfo.dimensions_flat == d)
233 fetch_future = q.fetch_page_async(limit, start_cursor=cursor)
234
235 # TODO(maruel): self.request.host_url should be the default AppEngine url
236 # version and not the current one. It is only an issue when
237 # version-dot-appid.appspot.com urls are used to access this page.
238 # TODO(aludwin): Display both gRPC and non-gRPC versions
239 version = bot_code.get_bot_version(self.request.host_url)
240 bots, cursor, more = fetch_future.get_result()
241 # Prefetch the tasks. We don't actually use the value here, it'll be
242 # implicitly used by ndb local's cache when refetched by the html template.
243 tasks = filter(None, (b.task for b in bots))
244 ndb.get_multi(tasks)
245 num_bots_busy = num_bots_busy_future.get_result()
246 num_bots_dead = num_bots_dead_future.get_result()
247 num_bots_quarantined = num_bots_quarantined_future.get_result()
248 num_bots_total = num_bots_total_future.get_result()
249 try_link = '/botlist?l=%d' % limit
198 if dimensions: 250 if dimensions:
199 new_ui_link += '&f=' + '&f='.join(dimensions) 251 try_link += '&f=' + '&f='.join(dimensions)
200 252 params = {
201 self.redirect(new_ui_link) 253 'bots': bots,
202 254 'current_version': version,
203 255 'cursor': cursor.urlsafe() if cursor and more else '',
204 class BotHandler(auth.AuthenticatingHandler): 256 'dimensions': '\n'.join(dimensions),
205 """Redirects to a page about the bot, including last tasks and events.""" 257 'is_admin': acl.is_admin(),
206 258 'is_privileged_user': acl.is_privileged_user(),
207 @auth.public 259 'limit': limit,
260 'now': now,
261 'num_bots_alive': num_bots_total - num_bots_dead,
262 'num_bots_busy': num_bots_busy,
263 'num_bots_dead': num_bots_dead,
264 'num_bots_quarantined': num_bots_quarantined,
265 'try_link': try_link,
266 'sort_by': sort_by,
267 'sort_options': self.SORT_OPTIONS,
268 'xsrf_token': self.generate_xsrf_token(),
269 }
270 self.response.write(
271 template.render('swarming/restricted_botslist.html', params))
272
273
274 class OldBotHandler(auth.AuthenticatingHandler):
275 """Returns data about the bot, including last tasks and events."""
276
277 @auth.autologin
278 @auth.require(acl.is_privileged_user)
208 def get(self, bot_id): 279 def get(self, bot_id):
209 self.redirect('/bot?id=%s' % bot_id) 280 # pagination is currently for tasks, not events.
281 limit = int(self.request.get('limit', 100))
282 cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor'))
283 run_results_future = task_result.TaskRunResult.query(
284 task_result.TaskRunResult.bot_id == bot_id).order(
285 -task_result.TaskRunResult.started_ts).fetch_page_async(
286 limit, start_cursor=cursor)
287 bot_future = bot_management.get_info_key(bot_id).get_async()
288 events_future = bot_management.get_events_query(
289 bot_id, True).fetch_async(100)
290
291 now = utils.utcnow()
292
293 # Calculate the time this bot was idle.
294 idle_time = datetime.timedelta()
295 run_time = datetime.timedelta()
296 run_results, cursor, more = run_results_future.get_result()
297 if run_results:
298 run_time = run_results[0].duration_now(now) or datetime.timedelta()
299 if not cursor and run_results[0].state != task_result.State.RUNNING:
300 # Add idle time since last task completed. Do not do this when a cursor
301 # is used since it's not representative.
302 idle_time = now - run_results[0].ended_ts
303 for index in xrange(1, len(run_results)):
304 # .started_ts will always be set by definition but .ended_ts may be None
305 # if the task was abandoned. We can't count idle time since the bot may
306 # have been busy running *another task*.
307 # TODO(maruel): One option is to add a third value "broken_time".
308 # Looking at timestamps specifically could help too, e.g. comparing
309 # ended_ts of this task vs the next one to see if the bot was assigned
310 # two tasks simultaneously.
311 if run_results[index].ended_ts:
312 idle_time += (
313 run_results[index-1].started_ts - run_results[index].ended_ts)
314 # We are taking the whole time the bot was doing work, not just the
315 # duration associated with the task.
316 duration = run_results[index].duration_as_seen_by_server
317 if duration:
318 run_time += duration
319
320 events = events_future.get_result()
321 bot = bot_future.get_result()
322 if not bot and events:
323 # If there is not BotInfo, look if there are BotEvent child of this
324 # entity. If this is the case, it means the bot was deleted but it's
325 # useful to show information about it to the user even if the bot was
326 # deleted. For example, it could be an auto-scaled bot.
327 bot = bot_management.BotInfo(
328 key=bot_management.get_info_key(bot_id),
329 dimensions_flat=bot_management.dimensions_to_flat(
330 events[0].dimensions),
331 state=events[0].state,
332 external_ip=events[0].external_ip,
333 authenticated_as=events[0].authenticated_as,
334 version=events[0].version,
335 quarantined=events[0].quarantined,
336 task_id=events[0].task_id,
337 last_seen_ts=events[0].ts)
338
339 params = {
340 'bot': bot,
341 'bot_id': bot_id,
342 # TODO(aludwin): Use the bot's correct gRPC status to determine the
343 # version
344 'current_version': bot_code.get_bot_version(self.request.host_url),
345 'cursor': cursor.urlsafe() if cursor and more else None,
346 'events': events,
347 'idle_time': idle_time,
348 'is_admin': acl.is_admin(),
349 'limit': limit,
350 'now': now,
351 'run_results': run_results,
352 'run_time': run_time,
353 'try_link': '/bot?id=%s' % bot_id,
354 'xsrf_token': self.generate_xsrf_token(),
355 }
356 self.response.write(
357 template.render('swarming/restricted_bot.html', params))
358
359
360 class OldBotDeleteHandler(auth.AuthenticatingHandler):
361 """Deletes a known bot.
362
363 This only deletes the BotInfo, not BotRoot, BotEvent's nor BotSettings.
364
365 This is sufficient so the bot doesn't show up on the Bots page while keeping
366 historical data.
367 """
368
369 @auth.require(acl.is_admin)
370 def post(self, bot_id):
371 bot_key = bot_management.get_info_key(bot_id)
372 if bot_key.get():
373 bot_key.delete()
374 self.redirect('/restricted/bots')
210 375
211 376
212 ### User accessible pages. 377 ### User accessible pages.
213 378
214 379
215 class TasksHandler(auth.AuthenticatingHandler): 380 class OldTasksHandler(auth.AuthenticatingHandler):
216 """Redirects to a list of all task requests.""" 381 """Lists all requests and allows callers to manage them."""
217 382 # Each entry is an item in the Sort column.
218 @auth.public 383 # Each entry is (key, text, hover)
384 SORT_CHOICES = [
385 ('created_ts', 'Created', 'Most recently created tasks are shown first.'),
386 ('modified_ts', 'Active',
387 'Shows the most recently active tasks first. Using this order resets '
388 'state to \'All\'.'),
389 ('completed_ts', 'Completed',
390 'Shows the most recently completed tasks first. Using this order resets '
391 'state to \'All\'.'),
392 ('abandoned_ts', 'Abandoned',
393 'Shows the most recently abandoned tasks first. Using this order resets '
394 'state to \'All\'.'),
395 ]
396
397 # Each list is one column in the Task state filtering column.
398 # Each sublist is the checkbox item in this column.
399 # Each entry is (key, text, hover)
400 # TODO(maruel): Evaluate what the categories the users would like for
401 # diagnosis, then adapt the DB to enable efficient queries.
402 STATE_CHOICES = [
403 [
404 ('all', 'All', 'All tasks ever requested independent of their state.'),
405 ('pending', 'Pending',
406 'Tasks that are still ready to be assigned to a bot. Using this order '
407 'resets order to \'Created\'.'),
408 ('running', 'Running',
409 'Tasks being currently executed by a bot. Using this order resets '
410 'order to \'Created\'.'),
411 ('pending_running', 'Pending|running',
412 'Tasks either \'pending\' or \'running\'. Using this order resets '
413 'order to \'Created\'.'),
414 ],
415 [
416 ('completed', 'Completed',
417 'All tasks that are completed, independent if the task itself '
418 'succeeded or failed. This excludes tasks that had an infrastructure '
419 'failure. Using this order resets order to \'Created\'.'),
420 ('completed_success', 'Successes',
421 'Tasks that completed successfully. Using this order resets order to '
422 '\'Created\'.'),
423 ('completed_failure', 'Failures',
424 'Tasks that were executed successfully but failed, e.g. exit code is '
425 'non-zero. Using this order resets order to \'Created\'.'),
426 ('timed_out', 'Timed out',
427 'The execution timed out, so it was forcibly killed.'),
428 ],
429 [
430 ('bot_died', 'Bot died',
431 'The bot stopped sending updates while running the task, causing the '
432 'task execution to time out. This is considered an infrastructure '
433 'failure and the usual reason is that the bot BSOD\'ed or '
434 'spontaneously rebooted. Using this order resets order to '
435 '\'Created\'.'),
436 ('expired', 'Expired',
437 'The task was not assigned a bot until its expiration timeout, causing '
438 'the task to never being assigned to a bot. This can happen when the '
439 'dimension filter was not available or overloaded with a low priority. '
440 'Either fix the priority or bring up more bots with these dimensions. '
441 'Using this order resets order to \'Created\'.'),
442 ('canceled', 'Canceled',
443 'The task was explictly canceled by a user before it started '
444 'executing. Using this order resets order to \'Created\'.'),
445 ],
446 ]
447
448 @auth.autologin
449 @auth.require(acl.is_user)
219 def get(self): 450 def get(self):
451 cursor_str = self.request.get('cursor')
220 limit = int(self.request.get('limit', 100)) 452 limit = int(self.request.get('limit', 100))
453 sort = self.request.get('sort', self.SORT_CHOICES[0][0])
454 state = self.request.get('state', self.STATE_CHOICES[0][0][0])
455 counts = self.request.get('counts', '').strip()
221 task_tags = [ 456 task_tags = [
222 line for line in self.request.get('task_tag', '').splitlines() if line 457 line for line in self.request.get('task_tag', '').splitlines() if line
223 ] 458 ]
224 459
225 new_ui_link = '/tasklist?l=%d' % limit 460 if not any(sort == i[0] for i in self.SORT_CHOICES):
461 self.abort(400, 'Invalid sort')
462 if not any(any(state == i[0] for i in j) for j in self.STATE_CHOICES):
463 self.abort(400, 'Invalid state')
464
465 if sort != 'created_ts':
466 # Zap all filters in this case to reduce the number of required indexes.
467 # Revisit according to the user requests.
468 state = 'all'
469
470 now = utils.utcnow()
471 # "Temporarily" disable the count. This is too slow on the prod server
472 # (>10s). The fix is to have the web page do a XHR query to get the values
473 # asynchronously.
474 counts_future = None
475 if counts == 'true':
476 counts_future = self._get_counts_future(now)
477
478 try:
479 if task_tags:
480 # Enforce created_ts when tags are used.
481 sort = 'created_ts'
482 query = task_result.get_result_summaries_query(
483 None, None, sort, state, task_tags)
484 tasks, cursor_str = datastore_utils.fetch_page(query, limit, cursor_str)
485
486 # Prefetch the TaskRequest all at once, so that ndb's in-process cache has
487 # it instead of fetching them one at a time indirectly when using
488 # TaskResultSummary.request_key.get().
489 futures = ndb.get_multi_async(t.request_key for t in tasks)
490
491 # Evaluate the counts to print the filtering columns with the associated
492 # numbers.
493 state_choices = self._get_state_choices(counts_future)
494 except ValueError as e:
495 self.abort(400, str(e))
496
497 def safe_sum(items):
498 return sum(items, datetime.timedelta())
499
500 def avg(items):
501 if not items:
502 return 0.
503 return safe_sum(items) / len(items)
504
505 def median(items):
506 if not items:
507 return 0.
508 middle = len(items) / 2
509 if len(items) % 2:
510 return items[middle]
511 return (items[middle-1]+items[middle]) / 2
512
513 gen = (t.duration_now(now) for t in tasks)
514 durations = sorted(t for t in gen if t is not None)
515 gen = (t.pending_now(now) for t in tasks)
516 pendings = sorted(t for t in gen if t is not None)
517 total_cost_usd = sum(t.cost_usd for t in tasks)
518 total_cost_saved_usd = sum(
519 t.cost_saved_usd for t in tasks if t.cost_saved_usd)
520 # Include the overhead in the total amount of time saved, since it's
521 # overhead saved.
522 # In theory, t.duration_as_seen_by_server should always be set when
523 # t.deduped_from is set but there has some broken entities in the datastore.
524 total_saved = safe_sum(
525 t.duration_as_seen_by_server for t in tasks
526 if t.deduped_from and t.duration_as_seen_by_server)
527 duration_sum = safe_sum(durations)
528 total_saved_percent = (
529 (100. * total_saved.total_seconds() / duration_sum.total_seconds())
530 if duration_sum else 0.)
531
532 try_link = '/tasklist?l=%d' % limit
226 if task_tags: 533 if task_tags:
227 new_ui_link += '&f=' + '&f='.join(task_tags) 534 try_link += '&f=' + '&f='.join(task_tags)
228 535 params = {
229 self.redirect(new_ui_link) 536 'cursor': cursor_str,
230 537 'duration_average': avg(durations),
231 538 'duration_median': median(durations),
232 class TaskHandler(auth.AuthenticatingHandler): 539 'duration_sum': duration_sum,
233 """Redirects to a page containing task request and result.""" 540 'has_pending': any(t.is_pending for t in tasks),
234 541 'has_running': any(t.is_running for t in tasks),
235 @auth.public 542 'is_admin': acl.is_admin(),
543 'is_privileged_user': acl.is_privileged_user(),
544 'limit': limit,
545 'now': now,
546 'pending_average': avg(pendings),
547 'pending_median': median(pendings),
548 'pending_sum': safe_sum(pendings),
549 'show_footer': bool(pendings or durations),
550 'sort': sort,
551 'sort_choices': self.SORT_CHOICES,
552 'state': state,
553 'state_choices': state_choices,
554 'task_tag': '\n'.join(task_tags),
555 'tasks': tasks,
556 'total_cost_usd': total_cost_usd,
557 'total_cost_saved_usd': total_cost_saved_usd,
558 'total_saved': total_saved,
559 'total_saved_percent': total_saved_percent,
560 'try_link': try_link,
561 'xsrf_token': self.generate_xsrf_token(),
562 }
563 # TODO(maruel): If admin or if the user is task's .user, show the Cancel
564 # button. Do not show otherwise.
565 self.response.write(template.render('swarming/user_tasks.html', params))
566
567 # Do not let dangling futures linger around.
568 ndb.Future.wait_all(futures)
569
570 def _get_counts_future(self, now):
571 """Returns all the counting futures in parallel."""
572 counts_future = {}
573 last_24h = now - datetime.timedelta(days=1)
574 for state_key, _, _ in itertools.chain.from_iterable(self.STATE_CHOICES):
575 query = task_result.get_result_summaries_query(
576 last_24h, None, 'created_ts', state_key, None)
577 counts_future[state_key] = query.count_async()
578 return counts_future
579
580 def _get_state_choices(self, counts_future):
581 """Converts STATE_CHOICES with _get_counts_future() into nice text."""
582 # Appends the number of tasks for each filter. It gives a sense of how much
583 # things are going on.
584 if counts_future:
585 counts = {k: v.get_result() for k, v in counts_future.iteritems()}
586 state_choices = []
587 for choice_list in self.STATE_CHOICES:
588 state_choices.append([])
589 for state_key, name, title in choice_list:
590 if counts_future:
591 name += ' (%d)' % counts[state_key]
592 state_choices[-1].append((state_key, name, title))
593 return state_choices
594
595
596 class BaseOldTaskHandler(auth.AuthenticatingHandler):
597 """Handler that acts on a single task.
598
599 Ensures that the user has access to the task.
600 """
601 def get_request_and_result(self, task_id, with_secret_bytes=False):
602 """Retrieves the TaskRequest for 'task_id' and enforces the ACL.
603
604 Supports both TaskResultSummary (ends with 0) or TaskRunResult (ends with 1
605 or 2).
606
607 Returns:
608 tuple(TaskRequest, SecretBytes, result): result can be either for
609 a TaskRunResult or a TaskResultSummay.
610 """
611 try:
612 key = task_pack.unpack_result_summary_key(task_id)
613 request_key = task_pack.result_summary_key_to_request_key(key)
614 except ValueError:
615 try:
616 key = task_pack.unpack_run_result_key(task_id)
617 request_key = task_pack.result_summary_key_to_request_key(
618 task_pack.run_result_key_to_result_summary_key(key))
619 except ValueError:
620 self.abort(404, 'Invalid key format.')
621 if with_secret_bytes:
622 sb_key = task_pack.request_key_to_secret_bytes_key(request_key)
623 request, result, secret_bytes = ndb.get_multi((request_key, key, sb_key))
624 else:
625 request, result = ndb.get_multi((request_key, key))
626 secret_bytes = None
627 if not request or not result:
628 self.abort(404, '%s not found.' % key.id())
629 if not request.has_access:
630 self.abort(403, '%s is not accessible.' % key.id())
631 return request, secret_bytes, result
632
633
634 class OldTaskHandler(BaseOldTaskHandler):
635 """Show the full text of a task request and its result."""
636
637 @staticmethod
638 def packages_grouped_by_path(flat_packages):
639 """Returns sorted [(path, [PinInfo, ...])].
640
641 Used by user_task.html.
642 """
643 retval = collections.defaultdict(list)
644 for pkg in flat_packages:
645 retval[pkg.path].append(pkg)
646 return sorted(retval.iteritems())
647
648 @auth.autologin
649 @auth.require(acl.is_user)
236 def get(self, task_id): 650 def get(self, task_id):
237 self.redirect('/task?id=%s' % task_id) 651 request, _, result = self.get_request_and_result(task_id)
652 parent_task_future = None
653 if request.parent_task_id:
654 parent_key = task_pack.unpack_run_result_key(request.parent_task_id)
655 parent_task_future = parent_key.get_async()
656 children_tasks_futures = [
657 task_pack.unpack_result_summary_key(c).get_async()
658 for c in result.children_task_ids
659 ]
660
661 bot_id = result.bot_id
662 following_task_future = None
663 previous_task_future = None
664 if result.started_ts:
665 # Use a shortcut name because it becomes unwieldy otherwise.
666 cls = task_result.TaskRunResult
667
668 # Note that the links will be to the TaskRunResult, not to
669 # TaskResultSummary.
670 following_task_future = cls.query(
671 cls.bot_id == bot_id,
672 cls.started_ts > result.started_ts,
673 ).order(cls.started_ts).get_async()
674 previous_task_future = cls.query(
675 cls.bot_id == bot_id,
676 cls.started_ts < result.started_ts,
677 ).order(-cls.started_ts).get_async()
678
679 bot_future = (
680 bot_management.get_info_key(bot_id).get_async() if bot_id else None)
681
682 following_task = None
683 if following_task_future:
684 following_task = following_task_future.get_result()
685
686 previous_task = None
687 if previous_task_future:
688 previous_task = previous_task_future.get_result()
689
690 parent_task = None
691 if parent_task_future:
692 parent_task = parent_task_future.get_result()
693 children_tasks = [c.get_result() for c in children_tasks_futures]
694
695 cipd = None
696 if request.properties.cipd_input:
697 cipd = {
698 'server': request.properties.cipd_input.server,
699 'client_package': request.properties.cipd_input.client_package,
700 'packages': self.packages_grouped_by_path(
701 request.properties.cipd_input.packages),
702 }
703
704 cipd_pins = None
705 if result.cipd_pins:
706 cipd_pins = {
707 'client_package': result.cipd_pins.client_package,
708 'packages': self.packages_grouped_by_path(result.cipd_pins.packages),
709 }
710
711 params = {
712 'bot': bot_future.get_result() if bot_future else None,
713 'children_tasks': children_tasks,
714 'cipd': cipd,
715 'cipd_pins': cipd_pins,
716 'is_admin': acl.is_admin(),
717 'is_gae_admin': users.is_current_user_admin(),
718 'is_privileged_user': acl.is_privileged_user(),
719 'following_task': following_task,
720 'full_appid': os.environ['APPLICATION_ID'],
721 'host_url': self.request.host_url,
722 'is_running': result.state == task_result.State.RUNNING,
723 'parent_task': parent_task,
724 'previous_task': previous_task,
725 'request': request,
726 'task': result,
727 'try_link': '/task?id=%s' % task_id,
728 'xsrf_token': self.generate_xsrf_token(),
729 }
730 self.response.write(template.render('swarming/user_task.html', params))
731
732
733 class OldTaskCancelHandler(BaseOldTaskHandler):
734 """Cancel a task."""
735
736 @auth.require(acl.is_user)
737 def post(self, task_id):
738 request, _, result = self.get_request_and_result(task_id)
739 if not task_scheduler.cancel_task(request, result.key)[0]:
740 self.abort(400, 'Task cancelation error')
741 # The cancel button appears at both the /tasks and /task pages. Redirect to
742 # the right place.
743 if self.request.get('redirect_to', '') == 'listing':
744 self.redirect('/user/tasks')
745 else:
746 self.redirect('/user/task/%s' % task_id)
747
748
749 class OldTaskRetryHandler(BaseOldTaskHandler):
750 """Retries the same task but with new metadata.
751
752 Retrying a task forcibly make it not idempotent so the task is unconditionally
753 scheduled.
754 """
755
756 @auth.require(acl.is_user)
757 def post(self, task_id):
758 original_request, secret_bytes, _ = self.get_request_and_result(
759 task_id, with_secret_bytes=True)
760 # Retrying a task is essentially reusing the same task request as the
761 # original one, but with new parameters.
762 new_request = task_request.new_request_clone(
763 original_request, secret_bytes,
764 allow_high_priority=acl.can_schedule_high_priority_tasks())
765 result_summary = task_scheduler.schedule_request(
766 new_request, secret_bytes)
767 self.redirect('/user/task/%s' % result_summary.task_id)
238 768
239 769
240 ### Public pages. 770 ### Public pages.
241 771
242 772
243 class OldUIHandler(auth.AuthenticatingHandler): 773 class OldUIHandler(auth.AuthenticatingHandler):
244 @auth.public 774 @auth.public
245 def get(self): 775 def get(self):
246 params = { 776 params = {
247 'host_url': self.request.host_url, 777 'host_url': self.request.host_url,
(...skipping 10 matching lines...) Expand all
258 params['mapreduce_jobs'] = [ 788 params['mapreduce_jobs'] = [
259 {'id': job_id, 'name': job_def['job_name']} 789 {'id': job_id, 'name': job_def['job_name']}
260 for job_id, job_def in mapreduce_jobs.MAPREDUCE_JOBS.iteritems() 790 for job_id, job_def in mapreduce_jobs.MAPREDUCE_JOBS.iteritems()
261 ] 791 ]
262 params['xsrf_token'] = self.generate_xsrf_token() 792 params['xsrf_token'] = self.generate_xsrf_token()
263 if acl.is_bootstrapper(): 793 if acl.is_bootstrapper():
264 params['bootstrap_token'] = bot_code.generate_bootstrap_token() 794 params['bootstrap_token'] = bot_code.generate_bootstrap_token()
265 self.response.write(template.render('swarming/root.html', params)) 795 self.response.write(template.render('swarming/root.html', params))
266 796
267 797
798 class BotsListHandler(auth.AuthenticatingHandler):
799 """Redirects to a list of known bots."""
800
801 @auth.public
802 def get(self):
803 limit = int(self.request.get('limit', 100))
804
805 dimensions = (
806 l.strip() for l in self.request.get('dimensions', '').splitlines()
807 )
808 dimensions = [i for i in dimensions if i]
809
810 new_ui_link = '/botlist?l=%d' % limit
811 if dimensions:
812 new_ui_link += '&f=' + '&f='.join(dimensions)
813
814 self.redirect(new_ui_link)
815
816
817 class BotHandler(auth.AuthenticatingHandler):
818 """Redirects to a page about the bot, including last tasks and events."""
819
820 @auth.public
821 def get(self, bot_id):
822 self.redirect('/bot?id=%s' % bot_id)
823
824
825 ### User accessible pages.
826
827
828 class TasksHandler(auth.AuthenticatingHandler):
829 """Redirects to a list of all task requests."""
830
831 @auth.public
832 def get(self):
833 limit = int(self.request.get('limit', 100))
834 task_tags = [
835 line for line in self.request.get('task_tag', '').splitlines() if line
836 ]
837
838 new_ui_link = '/tasklist?l=%d' % limit
839 if task_tags:
840 new_ui_link += '&f=' + '&f='.join(task_tags)
841
842 self.redirect(new_ui_link)
843
844
845 class TaskHandler(auth.AuthenticatingHandler):
846 """Redirects to a page containing task request and result."""
847
848 @auth.public
849 def get(self, task_id):
850 self.redirect('/task?id=%s' % task_id)
851
852
268 class UIHandler(auth.AuthenticatingHandler): 853 class UIHandler(auth.AuthenticatingHandler):
854 """Serves the landing page for the new UI of the requested page.
855
856 This landing page is stamped with the OAuth 2.0 client id from the
857 configuration."""
269 @auth.public 858 @auth.public
270 def get(self, page): 859 def get(self, page):
271 if not page: 860 if not page:
272 page = 'swarming' 861 page = 'swarming'
273 862
274 params = { 863 params = {
275 'client_id': config.settings().ui_client_id, 864 'client_id': config.settings().ui_client_id,
276 } 865 }
277 try: 866 try:
278 self.response.write(template.render( 867 self.response.write(template.render(
(...skipping 21 matching lines...) Expand all
300 template.bootstrap() 889 template.bootstrap()
301 utils.set_task_queue_module('default') 890 utils.set_task_queue_module('default')
302 891
303 routes = [ 892 routes = [
304 # Frontend pages. They return HTML. 893 # Frontend pages. They return HTML.
305 # Public pages. 894 # Public pages.
306 ('/oldui', OldUIHandler), 895 ('/oldui', OldUIHandler),
307 ('/stats', stats_gviz.StatsSummaryHandler), 896 ('/stats', stats_gviz.StatsSummaryHandler),
308 ('/<page:(bot|botlist|task|tasklist|)>', UIHandler), 897 ('/<page:(bot|botlist|task|tasklist|)>', UIHandler),
309 898
310 # User pages. 899 # Task pages. Redirects to Polymer UI
311 ('/user/tasks', TasksHandler), 900 ('/user/tasks', TasksHandler),
312 ('/user/task/<task_id:[0-9a-fA-F]+>', TaskHandler), 901 ('/user/task/<task_id:[0-9a-fA-F]+>', TaskHandler),
313 902
314 # Privileged user pages. 903 # Bot pages. Redirects to Polymer UI
315 ('/restricted/bots', BotsListHandler), 904 ('/restricted/bots', BotsListHandler),
316 ('/restricted/bot/<bot_id:[^/]+>', BotHandler), 905 ('/restricted/bot/<bot_id:[^/]+>', BotHandler),
317 906
907 # User pages. TODO(kjlubick): Remove on January 1, 2017
908 ('/oldui/user/tasks', OldTasksHandler),
909 ('/oldui/user/task/<task_id:[0-9a-fA-F]+>', OldTaskHandler),
910 ('/oldui/user/task/<task_id:[0-9a-fA-F]+>/cancel', OldTaskCancelHandler),
911 ('/oldui/user/task/<task_id:[0-9a-fA-F]+>/retry', OldTaskRetryHandler),
912
913 # Privileged user pages. TODO(kjlubick): Remove on January 1, 2017
914 ('/oldui/restricted/bots', OldBotsListHandler),
915 ('/oldui/restricted/bot/<bot_id:[^/]+>', OldBotHandler),
916 ('/oldui/restricted/bot/<bot_id:[^/]+>/delete', OldBotDeleteHandler),
917
318 # Admin pages. 918 # Admin pages.
319 ('/restricted/config', RestrictedConfigHandler), 919 ('/restricted/config', RestrictedConfigHandler),
320 ('/restricted/cancel_pending', RestrictedCancelPendingHandler), 920 ('/restricted/cancel_pending', RestrictedCancelPendingHandler),
321 ('/restricted/upload/bot_config', UploadBotConfigHandler), 921 ('/restricted/upload/bot_config', UploadBotConfigHandler),
322 ('/restricted/upload/bootstrap', UploadBootstrapHandler), 922 ('/restricted/upload/bootstrap', UploadBootstrapHandler),
323 923
324 # Mapreduce related urls. 924 # Mapreduce related urls.
325 (r'/restricted/launch_mapreduce', RestrictedLaunchMapReduceJob), 925 (r'/restricted/launch_mapreduce', RestrictedLaunchMapReduceJob),
326 926
327 # The new APIs: 927 # The new APIs:
328 ('/swarming/api/v1/stats/summary/<resolution:[a-z]+>', 928 ('/swarming/api/v1/stats/summary/<resolution:[a-z]+>',
329 stats_gviz.StatsGvizSummaryHandler), 929 stats_gviz.StatsGvizSummaryHandler),
330 ('/swarming/api/v1/stats/dimensions/<dimensions:.+>/<resolution:[a-z]+>', 930 ('/swarming/api/v1/stats/dimensions/<dimensions:.+>/<resolution:[a-z]+>',
331 stats_gviz.StatsGvizDimensionsHandler), 931 stats_gviz.StatsGvizDimensionsHandler),
332 932
333 ('/_ah/mail/<to:.+>', EmailHandler), 933 ('/_ah/mail/<to:.+>', EmailHandler),
334 ('/_ah/warmup', WarmupHandler), 934 ('/_ah/warmup', WarmupHandler),
335 ] 935 ]
336 routes = [webapp2.Route(*i) for i in routes] 936 routes = [webapp2.Route(*i) for i in routes]
337 937
338 # If running on a local dev server, allow bots to connect without prior 938 # If running on a local dev server, allow bots to connect without prior
339 # groups configuration. Useful when running smoke test. 939 # groups configuration. Useful when running smoke test.
340 if utils.is_local_dev_server(): 940 if utils.is_local_dev_server():
341 acl.bootstrap_dev_server_acls() 941 acl.bootstrap_dev_server_acls()
342 942
343 routes.extend(handlers_backend.get_routes()) 943 routes.extend(handlers_backend.get_routes())
344 routes.extend(handlers_bot.get_routes()) 944 routes.extend(handlers_bot.get_routes())
345 routes.extend(handlers_endpoints.get_routes()) 945 routes.extend(handlers_endpoints.get_routes())
346 return webapp2.WSGIApplication(routes, debug=debug) 946 return webapp2.WSGIApplication(routes, debug=debug)
OLDNEW
« no previous file with comments | « no previous file | appengine/swarming/templates/bot_view.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698