| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import datetime | 6 import datetime |
| 7 import math | 7 import math |
| 8 import os | 8 import os |
| 9 import sys | 9 import sys |
| 10 import time | 10 import time |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 clovis_logger = logging.getLogger('clovis_frontend') | 31 clovis_logger = logging.getLogger('clovis_frontend') |
| 32 clovis_logger.setLevel(logging.DEBUG) | 32 clovis_logger.setLevel(logging.DEBUG) |
| 33 project_name = app_identity.get_application_id() | 33 project_name = app_identity.get_application_id() |
| 34 instance_helper = common.google_instance_helper.GoogleInstanceHelper( | 34 instance_helper = common.google_instance_helper.GoogleInstanceHelper( |
| 35 credentials=GoogleCredentials.get_application_default(), | 35 credentials=GoogleCredentials.get_application_default(), |
| 36 project=project_name, | 36 project=project_name, |
| 37 logger=clovis_logger) | 37 logger=clovis_logger) |
| 38 app = flask.Flask(__name__) | 38 app = flask.Flask(__name__) |
| 39 | 39 |
| 40 | 40 |
| 41 def Render(message, memory_logs=None): | 41 def RenderJobCreationPage(message, memory_logs=None): |
| 42 """Renders the log.html template. | 42 """Renders the log.html template. |
| 43 | 43 |
| 44 Args: | 44 Args: |
| 45 message (str): Main content of the page. | 45 message (str): Main content of the page. |
| 46 memory_logs (MemoryLogs): Optional logs. | 46 memory_logs (MemoryLogs): Optional logs. |
| 47 """ | 47 """ |
| 48 log = None | 48 log = None |
| 49 if memory_logs: | 49 if memory_logs: |
| 50 log = memory_logs.Flush().split('\n') | 50 log = memory_logs.Flush().split('\n') |
| 51 return flask.render_template('log.html', body=message, log=log) | 51 return flask.render_template('log.html', body=message, log=log, |
| 52 title='Job Creation Status') |
| 52 | 53 |
| 53 | 54 |
| 54 def PollWorkers(tag, start_time, timeout_hours, email_address, task_url): | 55 def PollWorkers(tag, start_time, timeout_hours, email_address, task_url): |
| 55 """Checks if there are workers associated with tag, by polling the instance | 56 """Checks if there are workers associated with tag, by polling the instance |
| 56 group. When all workers are finished, the instance group and the instance | 57 group. When all workers are finished, the instance group and the instance |
| 57 template are destroyed. | 58 template are destroyed. |
| 58 After some timeout delay, the instance group is destroyed even if there are | 59 After some timeout delay, the instance group is destroyed even if there are |
| 59 still workers associated to it, which has the effect of killing all these | 60 still workers associated to it, which has the effect of killing all these |
| 60 workers. | 61 workers. |
| 61 | 62 |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 316 def StartFromJsonString(http_body_str): | 317 def StartFromJsonString(http_body_str): |
| 317 """Main function handling a JSON task posted by the user.""" | 318 """Main function handling a JSON task posted by the user.""" |
| 318 # Set up logging. | 319 # Set up logging. |
| 319 memory_logs = MemoryLogs(clovis_logger) | 320 memory_logs = MemoryLogs(clovis_logger) |
| 320 memory_logs.Start() | 321 memory_logs.Start() |
| 321 | 322 |
| 322 # Load the task from JSON. | 323 # Load the task from JSON. |
| 323 task = ClovisTask.FromJsonString(http_body_str) | 324 task = ClovisTask.FromJsonString(http_body_str) |
| 324 if not task: | 325 if not task: |
| 325 clovis_logger.error('Invalid JSON task.') | 326 clovis_logger.error('Invalid JSON task.') |
| 326 return Render('Invalid JSON task:\n' + http_body_str, memory_logs) | 327 return RenderJobCreationPage( |
| 328 'Invalid JSON task:\n' + http_body_str, memory_logs) |
| 327 | 329 |
| 328 task_tag = task.BackendParams()['tag'] | 330 task_tag = task.BackendParams()['tag'] |
| 329 clovis_logger.info('Start processing %s task with tag %s.' % (task.Action(), | 331 clovis_logger.info('Start processing %s task with tag %s.' % (task.Action(), |
| 330 task_tag)) | 332 task_tag)) |
| 331 user_email = email_helper.GetUserEmail() | 333 user_email = email_helper.GetUserEmail() |
| 332 | 334 |
| 333 # Write the job to the datastore. | 335 # Write the job to the datastore. |
| 334 frontend_job = FrontendJob.CreateForTag(task_tag) | 336 frontend_job = FrontendJob.CreateForTag(task_tag) |
| 335 frontend_job.email = user_email | 337 frontend_job.email = user_email |
| 336 frontend_job.status = 'not_started' | 338 frontend_job.status = 'not_started' |
| 337 frontend_job.clovis_task = task.ToJsonString() | 339 frontend_job.clovis_task = task.ToJsonString() |
| 338 frontend_job.put() | 340 frontend_job.put() |
| 339 | 341 |
| 340 # Process the job on the queue, to avoid timeout issues. | 342 # Process the job on the queue, to avoid timeout issues. |
| 341 deferred.defer(SpawnTasksOnBackgroundQueue, task_tag) | 343 deferred.defer(SpawnTasksOnBackgroundQueue, task_tag) |
| 342 | 344 |
| 343 return Render( | 345 return RenderJobCreationPage( |
| 344 flask.Markup( | 346 flask.Markup( |
| 345 '<a href="%s">See progress.</a>' % FrontendJob.GetJobURL(task_tag)), | 347 '<a href="%s">See progress.</a>' % FrontendJob.GetJobURL(task_tag)), |
| 346 memory_logs) | 348 memory_logs) |
| 347 | 349 |
| 348 | 350 |
| 349 def SpawnTasksOnBackgroundQueue(task_tag): | 351 def SpawnTasksOnBackgroundQueue(task_tag): |
| 350 """Spawns Clovis tasks associated with task_tag from the backgound queue. | 352 """Spawns Clovis tasks associated with task_tag from the backgound queue. |
| 351 | 353 |
| 352 This function is mostly a wrapper around SpawnTasks() that catches exceptions. | 354 This function is mostly a wrapper around SpawnTasks() that catches exceptions. |
| 353 It is assumed that a FrontendJob for task_tag exists. | 355 It is assumed that a FrontendJob for task_tag exists. |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 429 sequential_duration_s / target_parallel_duration_s) | 431 sequential_duration_s / target_parallel_duration_s) |
| 430 | 432 |
| 431 # Check the instance quotas. | 433 # Check the instance quotas. |
| 432 clovis_logger.info( | 434 clovis_logger.info( |
| 433 'Requesting %i instances.' % task.BackendParams()['instance_count']) | 435 'Requesting %i instances.' % task.BackendParams()['instance_count']) |
| 434 frontend_job.status = 'checking_instance_quotas' | 436 frontend_job.status = 'checking_instance_quotas' |
| 435 max_instances = instance_helper.GetAvailableInstanceCount() | 437 max_instances = instance_helper.GetAvailableInstanceCount() |
| 436 if max_instances == -1: | 438 if max_instances == -1: |
| 437 frontend_job.status = 'instance_count_error' | 439 frontend_job.status = 'instance_count_error' |
| 438 return | 440 return |
| 439 elif task.BackendParams()['instance_count'] == 0: | 441 elif max_instances == 0 and task.BackendParams()['instance_count'] > 0: |
| 440 frontend_job.status = 'no_instance_available_error' | 442 frontend_job.status = 'no_instance_available_error' |
| 441 return | 443 return |
| 442 elif max_instances < task.BackendParams()['instance_count']: | 444 elif max_instances < task.BackendParams()['instance_count']: |
| 443 clovis_logger.warning( | 445 clovis_logger.warning( |
| 444 'Instance count limited by quota: %i available / %i requested.' % ( | 446 'Instance count limited by quota: %i available / %i requested.' % ( |
| 445 max_instances, task.BackendParams()['instance_count'])) | 447 max_instances, task.BackendParams()['instance_count'])) |
| 446 task.BackendParams()['instance_count'] = max_instances | 448 task.BackendParams()['instance_count'] = max_instances |
| 447 | 449 |
| 448 # Compute the timeout if there is none specified. | 450 # Compute the timeout if there is none specified. |
| 449 expected_duration_s = sequential_duration_s / ( | 451 expected_duration_s = sequential_duration_s / ( |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 514 def Root(): | 516 def Root(): |
| 515 """Home page: show the new task form.""" | 517 """Home page: show the new task form.""" |
| 516 return flask.render_template('form.html') | 518 return flask.render_template('form.html') |
| 517 | 519 |
| 518 | 520 |
| 519 @app.route('/form_sent', methods=['POST']) | 521 @app.route('/form_sent', methods=['POST']) |
| 520 def StartFromForm(): | 522 def StartFromForm(): |
| 521 """HTML form endpoint.""" | 523 """HTML form endpoint.""" |
| 522 data_stream = flask.request.files.get('json_task') | 524 data_stream = flask.request.files.get('json_task') |
| 523 if not data_stream: | 525 if not data_stream: |
| 524 return Render('Failed, no content.') | 526 return RenderJobCreationPage('Failed, no content.') |
| 525 http_body_str = data_stream.read() | 527 http_body_str = data_stream.read() |
| 526 return StartFromJsonString(http_body_str) | 528 return StartFromJsonString(http_body_str) |
| 527 | 529 |
| 528 | 530 |
| 531 @app.route('/kill_job') |
| 532 def KillJob(): |
| 533 tag = flask.request.args.get('tag') |
| 534 page_title = 'Kill Job' |
| 535 if not tag: |
| 536 return flask.render_template('log.html', body='Failed: Invalid tag.', |
| 537 title=page_title) |
| 538 |
| 539 frontend_job = FrontendJob.GetFromTag(tag) |
| 540 |
| 541 if not frontend_job: |
| 542 return flask.render_template('log.html', body='Job not found.', |
| 543 title=page_title) |
| 544 Finalize(tag, frontend_job.email, 'CANCELED', frontend_job.task_url) |
| 545 |
| 546 body = 'Killed job %s.' % tag |
| 547 return flask.render_template('log.html', body=body, title=page_title) |
| 548 |
| 549 |
| 529 @app.route('/list_jobs') | 550 @app.route('/list_jobs') |
| 530 def ShowTaskList(): | 551 def ShowJobList(): |
| 531 """Shows a list of all active jobs.""" | 552 """Shows a list of all active jobs.""" |
| 532 tags = FrontendJob.ListJobs() | 553 tags = FrontendJob.ListJobs() |
| 554 page_title = 'Active Jobs' |
| 533 | 555 |
| 534 if not tags: | 556 if not tags: |
| 535 Render('No active jobs.') | 557 return flask.render_template('log.html', body='No active job.', |
| 558 title=page_title) |
| 536 | 559 |
| 537 html = flask.Markup('Active tasks:<ul>') | 560 html = '' |
| 538 for tag in tags: | 561 for tag in tags: |
| 539 html += flask.Markup( | 562 html += flask.Markup( |
| 540 '<li><a href="%s">%s</a></li>') % (FrontendJob.GetJobURL(tag), tag) | 563 '<li><a href="%s">%s</a></li>') % (FrontendJob.GetJobURL(tag), tag) |
| 541 html += flask.Markup('</ul>') | 564 html += flask.Markup('</ul>') |
| 542 return Render(html) | 565 return flask.render_template('log.html', body=html, title=page_title) |
| 543 | 566 |
| 544 | 567 |
| 545 @app.route(FrontendJob.SHOW_JOB_URL) | 568 @app.route(FrontendJob.SHOW_JOB_URL) |
| 546 def ShowTask(): | 569 def ShowJob(): |
| 547 """Shows basic information abour a job.""" | 570 """Shows basic information abour a job.""" |
| 548 tag = flask.request.args.get('tag') | 571 tag = flask.request.args.get('tag') |
| 572 page_title = 'Job Information' |
| 549 if not tag: | 573 if not tag: |
| 550 return Render('Invalid task tag.') | 574 return flask.render_template('log.html', body='Invalid tag.', |
| 575 title=page_title) |
| 551 | 576 |
| 552 frontend_job = FrontendJob.GetFromTag(tag) | 577 frontend_job = FrontendJob.GetFromTag(tag) |
| 553 | 578 |
| 554 if not frontend_job: | 579 if not frontend_job: |
| 555 return Render('Task not found.') | 580 return flask.render_template('log.html', body='Job not found.', |
| 556 | 581 title=page_title) |
| 557 message = flask.Markup('Task details:' + frontend_job.RenderAsHtml()) | |
| 558 | 582 |
| 559 log = None | 583 log = None |
| 560 if frontend_job.log: | 584 if frontend_job.log: |
| 561 log = frontend_job.log.split('\n') | 585 log = frontend_job.log.split('\n') |
| 562 | 586 |
| 563 return flask.render_template('log.html', body=message, log=log) | 587 body = flask.Markup(frontend_job.RenderAsHtml()) |
| 588 body += flask.Markup('<a href="/kill_job?tag=%s">Kill</a>' % tag) |
| 589 return flask.render_template('log.html', log=log, title=page_title, |
| 590 body=body) |
| 564 | 591 |
| 565 | 592 |
| 566 @app.errorhandler(404) | 593 @app.errorhandler(404) |
| 567 def PageNotFound(e): # pylint: disable=unused-argument | 594 def PageNotFound(e): # pylint: disable=unused-argument |
| 568 """Return a custom 404 error.""" | 595 """Return a custom 404 error.""" |
| 569 return 'Sorry, Nothing at this URL.', 404 | 596 return 'Sorry, Nothing at this URL.', 404 |
| 570 | 597 |
| 571 | 598 |
| 572 @app.errorhandler(500) | 599 @app.errorhandler(500) |
| 573 def ApplicationError(e): | 600 def ApplicationError(e): |
| 574 """Return a custom 500 error.""" | 601 """Return a custom 500 error.""" |
| 575 return 'Sorry, unexpected error: {}'.format(e), 499 | 602 return 'Sorry, unexpected error: {}'.format(e), 499 |
| OLD | NEW |