blob: f1410999b30a85aa60a3668474589312ccf9fd1d [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides the web interface for changing internal_only property of a Bot."""
import logging
from google.appengine.api import taskqueue
from google.appengine.datastore import datastore_query
from google.appengine.ext import ndb
from dashboard import datastore_hooks
from dashboard import request_handler
from dashboard.models import anomaly
from dashboard.models import graph_data
# Number of Row entities to process at once.
_MAX_ROWS_TO_PUT = 25
# Number of Test entities to process at once.
_MAX_TESTS_TO_PUT = 25
# Which queue to use for tasks started by this handler. Must be in queue.yaml.
_QUEUE_NAME = 'migrate-queue'
class ChangeInternalOnlyHandler(request_handler.RequestHandler):
"""Changes internal_only property of Bot, Test, and Row."""
def get(self):
"""Renders the UI for selecting bots."""
masters = {}
bots = graph_data.Bot.query().fetch()
for bot in bots:
master_name = bot.key.parent().string_id()
bot_name = bot.key.string_id()
bots = masters.setdefault(master_name, [])
bots.append({
'name': bot_name,
'internal_only': bot.internal_only,
})
logging.info('MASTERS: %s', masters)
self.RenderHtml('change_internal_only.html', {
'masters': masters,
})
def post(self):
"""Updates the selected bots internal_only_property.
POST requests will be made by the task queue; tasks are added to the task
queue either by a kick-off POST from the front-end form, or by this handler
itself.
Request parameters:
internal_only: "true" if turning on internal_only, else "false".
bots: Bots to update. Multiple bots parameters are possible; the value
of each should be a string like "MasterName/platform-name".
test: An urlsafe Key for a Test entity.
cursor: An urlsafe Cursor; this parameter is only given if we're part-way
through processing a Bot or a Test.
Outputs:
A message to the user if this request was started by the web form,
or an error message if something went wrong, or nothing.
"""
# /change_internal_only should be only accessible if one has administrator
# privileges, so requests are guaranteed to be authorized.
datastore_hooks.SetPrivilegedRequest()
internal_only_string = self.request.get('internal_only')
if internal_only_string == 'true':
internal_only = True
elif internal_only_string == 'false':
internal_only = False
else:
self.ReportError('No internal_only field')
return
bot_names = self.request.get_all('bots')
test_key_urlsafe = self.request.get('test')
cursor = self.request.get('cursor', None)
if bot_names and len(bot_names) > 1:
self._UpdateMultipleBots(bot_names, internal_only)
self.RenderHtml('result.html', {
'headline': ('Updating internal_only. This may take some time '
'depending on the data to update. Check the task queue '
'to determine whether the job is still in progress.'),
})
elif bot_names and len(bot_names) == 1:
self._UpdateBot(bot_names[0], internal_only, cursor=cursor)
elif test_key_urlsafe:
self._UpdateTest(test_key_urlsafe, internal_only, cursor=cursor)
def _UpdateMultipleBots(self, bot_names, internal_only):
"""Kicks off update tasks for individual bots and their tests."""
for bot_name in bot_names:
taskqueue.add(
url='/change_internal_only',
params={
'bots': bot_name,
'internal_only': 'true' if internal_only else 'false'
},
queue_name=_QUEUE_NAME)
def _UpdateBot(self, bot_name, internal_only, cursor=None):
"""Start updating internal_only for the given bot and associated data."""
master, bot = bot_name.split('/')
bot_key = ndb.Key('Master', master, 'Bot', bot)
if not cursor:
# First time updating for this Bot.
bot_entity = bot_key.get()
if bot_entity.internal_only != internal_only:
bot_entity.internal_only = internal_only
bot_entity.put()
else:
cursor = datastore_query.Cursor(urlsafe=cursor)
# Fetch a certain number of Test entities starting from cursor. See:
# https://developers.google.com/appengine/docs/python/ndb/queryclass
# Start update tasks for each existing subordinate Test.
test_query = graph_data.Test.query(ancestor=bot_key)
test_keys, next_cursor, more = test_query.fetch_page(
_MAX_TESTS_TO_PUT, start_cursor=cursor, keys_only=True)
for test_key in test_keys:
taskqueue.add(
url='/change_internal_only',
params={
'test': test_key.urlsafe(),
'internal_only': 'true' if internal_only else 'false',
},
queue_name=_QUEUE_NAME)
if more:
taskqueue.add(
url='/change_internal_only',
params={
'bots': bot_name,
'cursor': next_cursor.urlsafe(),
'internal_only': 'true' if internal_only else 'false',
},
queue_name=_QUEUE_NAME)
def _UpdateTest(self, test_key_urlsafe, internal_only, cursor=None):
"""Updates the given Test and associated Row entities."""
test_key = ndb.Key(urlsafe=test_key_urlsafe)
if not cursor:
# First time updating for this Test.
test_entity = test_key.get()
if test_entity.internal_only != internal_only:
test_entity.internal_only = internal_only
test_entity.put()
# Update all of the Anomaly entities for this test.
# Assuming that this should be fast enough to do in one request
# for any one test.
query = anomaly.Anomaly.query(anomaly.Anomaly.test == test_key)
anomalies = query.fetch()
for anomaly_entity in anomalies:
if anomaly_entity.internal_only != internal_only:
anomaly_entity.internal_only = internal_only
ndb.put_multi(anomalies)
else:
cursor = datastore_query.Cursor(urlsafe=cursor)
# Fetch a certain number of Row entities starting from cursor.
rows_query = graph_data.Row.query(graph_data.Row.parent_test == test_key)
rows, next_cursor, more = rows_query.fetch_page(
_MAX_ROWS_TO_PUT, start_cursor=cursor)
for row in rows:
if row.internal_only != internal_only:
row.internal_only = internal_only
ndb.put_multi(rows)
if more:
taskqueue.add(
url='/change_internal_only',
params={
'test': test_key_urlsafe,
'cursor': next_cursor.urlsafe(),
'internal_only': 'true' if internal_only else 'false',
},
queue_name=_QUEUE_NAME)