1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# found in the LICENSE file.
4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import logging
6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import traceback
7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
8a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from app_yaml_helper import AppYamlHelper
9a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from appengine_wrappers import (
100f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    GetAppVersion, IsDeadlineExceededError, logservice)
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from branch_utility import BranchUtility
12a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from compiled_file_system import CompiledFileSystem
13424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from data_source_registry import CreateDataSources
140f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)from environment import IsDevServer
15d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)from extensions_paths import EXAMPLES, PUBLIC_TEMPLATES, STATIC_DOCS
16ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochfrom file_system_util import CreateURLsFromPaths
17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)from future import Gettable, Future
181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from github_file_system_provider import GithubFileSystemProvider
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)from host_file_system_provider import HostFileSystemProvider
20b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)from object_store_creator import ObjectStoreCreator
21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from render_servlet import RenderServlet
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from server_instance import ServerInstance
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from servlet import Servlet, Request, Response
24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)from timer import Timer, TimerClosure
25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
26b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
27b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)class _SingletonRenderServletDelegate(RenderServlet.Delegate):
28b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  def __init__(self, server_instance):
29b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    self._server_instance = server_instance
30b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def CreateServerInstance(self):
32b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    return self._server_instance
33c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class _CronLogger(object):
353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''Wraps the logging.* methods to prefix them with 'cron' and flush
363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  immediately. The flushing is important because often these cron runs time
373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  out and we lose the logs.
383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''
393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def info(self, msg, *args):    self._log(logging.info, msg, args)
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def warning(self, msg, *args): self._log(logging.warning, msg, args)
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def error(self, msg, *args):   self._log(logging.error, msg, args)
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def _log(self, logfn, msg, args):
443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    try:
453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      logfn('cron: %s' % msg, *args)
463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    finally:
473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      logservice.flush()
483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)_cronlog = _CronLogger()
503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)def _RequestEachItem(title, items, request_callback):
523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''Runs a task |request_callback| named |title| for each item in |items|.
533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  |request_callback| must take an item and return a servlet response.
543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  Returns true if every item was successfully run, false if any return a
553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  non-200 response or raise an exception.
563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''
573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  _cronlog.info('%s: starting', title)
583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  success_count, failure_count = 0, 0
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  timer = Timer()
603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  try:
613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    for i, item in enumerate(items):
623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      def error_message(detail):
633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        return '%s: error rendering %s (%s of %s): %s' % (
643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            title, item, i + 1, len(items), detail)
653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      try:
663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        response = request_callback(item)
673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        if response.status == 200:
683551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          success_count += 1
693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        else:
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          _cronlog.error(error_message('response status %s' % response.status))
713551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          failure_count += 1
723551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      except Exception as e:
733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        _cronlog.error(error_message(traceback.format_exc()))
743551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        failure_count += 1
753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        if IsDeadlineExceededError(e): raise
763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  finally:
77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    _cronlog.info('%s: rendered %s of %s with %s failures in %s',
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        title, success_count, len(items), failure_count,
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        timer.Stop().FormatElapsed())
803551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return success_count == len(items)
813551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)class CronServlet(Servlet):
83c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''Servlet which runs a cron job.
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''
85b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  def __init__(self, request, delegate_for_test=None):
86b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    Servlet.__init__(self, request)
87b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    self._delegate = delegate_for_test or CronServlet.Delegate()
88b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
89b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  class Delegate(object):
90a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''CronServlet's runtime dependencies. Override for testing.
91b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    '''
92b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    def CreateBranchUtility(self, object_store_creator):
93b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      return BranchUtility.Create(object_store_creator)
94b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def CreateHostFileSystemProvider(self,
964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                     object_store_creator,
974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                     max_trunk_revision=None):
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return HostFileSystemProvider(object_store_creator,
994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                    max_trunk_revision=max_trunk_revision)
100b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    def CreateGithubFileSystemProvider(self, object_store_creator):
1021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      return GithubFileSystemProvider(object_store_creator)
103b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
104a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    def GetAppVersion(self):
105a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return GetAppVersion()
106a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def Get(self):
1083551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # Crons often time out, and if they do we need to make sure to flush the
1093551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # logs before the process gets killed (Python gives us a couple of
1103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # seconds).
1113551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    #
1123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # So, manually flush logs at the end of the cron run. However, sometimes
1133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # even that isn't enough, which is why in this file we use _cronlog and
1143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # make it flush the log every time its used.
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    logservice.AUTOFLUSH_ENABLED = False
116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self._GetImpl()
1183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    except BaseException:
1193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      _cronlog.error('Caught top-level exception! %s', traceback.format_exc())
120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    finally:
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      logservice.flush()
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def _GetImpl(self):
124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Cron strategy:
125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    #
126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Find all public template files and static files, and render them. Most of
127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # the time these won't have changed since the last cron run, so it's a
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # little wasteful, but hopefully rendering is really fast (if it isn't we
129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # have a problem).
1303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    _cronlog.info('starting')
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
132b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    # This is returned every time RenderServlet wants to create a new
133b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    # ServerInstance.
1343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    #
1353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # TODO(kalman): IMPORTANT. This sometimes throws an exception, breaking
1363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # everything. Need retry logic at the fetcher level.
137a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    server_instance = self._GetSafeServerInstance()
1384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    trunk_fs = server_instance.host_file_system_provider.GetTrunk()
139b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    def render(path):
141eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      request = Request(path, self._request.host, self._request.headers)
142eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      delegate = _SingletonRenderServletDelegate(server_instance)
143eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return RenderServlet(request, delegate).Get()
144c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    def request_files_in_dir(path, prefix=''):
1463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      '''Requests every file found under |path| in this host file system, with
1473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      a request prefix of |prefix|.
1483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      '''
1494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      files = [name for name, _ in CreateURLsFromPaths(trunk_fs, path, prefix)]
1503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return _RequestEachItem(path, files, render)
1513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    results = []
153a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
1543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    try:
155f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Start running the hand-written Cron methods first; they can be run in
156f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # parallel. They are resolved at the end.
157f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      def run_cron_for_future(target):
158f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        title = target.__class__.__name__
159f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        future, init_timer = TimerClosure(target.Cron)
160f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        assert isinstance(future, Future), (
161f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '%s.Cron() did not return a Future' % title)
162f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        def resolve():
163f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          resolve_timer = Timer()
164f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          try:
165f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            future.Get()
166f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          except Exception as e:
167f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            _cronlog.error('%s: error %s' % (title, traceback.format_exc()))
168f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            results.append(False)
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if IsDeadlineExceededError(e): raise
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          finally:
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            resolve_timer.Stop()
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            _cronlog.info('%s took %s: %s to initialize and %s to resolve' %
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                (title,
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 init_timer.With(resolve_timer).FormatElapsed(),
175f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 init_timer.FormatElapsed(),
176f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 resolve_timer.FormatElapsed()))
177f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return Future(delegate=Gettable(resolve))
178f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
179f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      targets = (CreateDataSources(server_instance).values() +
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 [server_instance.content_providers])
181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      title = 'initializing %s parallel Cron targets' % len(targets)
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      _cronlog.info(title)
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      timer = Timer()
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        cron_futures = [run_cron_for_future(target) for target in targets]
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      finally:
187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        _cronlog.info('%s took %s' % (title, timer.Stop().FormatElapsed()))
188f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1893551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # Rendering the public templates will also pull in all of the private
1903551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # templates.
191f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      results.append(request_files_in_dir(PUBLIC_TEMPLATES))
192b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1933551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # Rendering the public templates will have pulled in the .js and
1943551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # manifest.json files (for listing examples on the API reference pages),
1953551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # but there are still images, CSS, etc.
196f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      results.append(request_files_in_dir(STATIC_DOCS, prefix='static'))
197b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # Samples are too expensive to run on the dev server, where there is no
1993551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # parallel fetch.
200a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      if not IsDevServer():
2013551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        # Fetch each individual sample file.
202f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        results.append(request_files_in_dir(EXAMPLES,
203f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                            prefix='extensions/examples'))
2043551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
2053551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        # Fetch the zip file of each example (contains all the individual
2063551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        # files).
207a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)        example_zips = []
208f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        for root, _, files in trunk_fs.Walk(EXAMPLES):
209a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          example_zips.extend(
2103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)              root + '.zip' for name in files if name == 'manifest.json')
2113551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        results.append(_RequestEachItem(
2123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            'example zips',
2133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            example_zips,
2143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            lambda path: render('extensions/examples/' + path)))
2153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
216f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Resolve the hand-written Cron method futures.
217f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      title = 'resolving %s parallel Cron targets' % len(targets)
218f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      _cronlog.info(title)
219f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      timer = Timer()
220f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
221f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        for future in cron_futures:
222f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          future.Get()
223f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      finally:
224f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        _cronlog.info('%s took %s' % (title, timer.Stop().FormatElapsed()))
225eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
226d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    except:
2273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      results.append(False)
2283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # This should never actually happen (each cron step does its own
2293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # conservative error checking), so re-raise no matter what it is.
230d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      _cronlog.error('uncaught error: %s' % traceback.format_exc())
2313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      raise
2323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    finally:
2333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      success = all(results)
2343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      _cronlog.info('finished (%s)', 'success' if success else 'FAILED')
2353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return (Response.Ok('Success') if success else
2363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)              Response.InternalError('Failure'))
237a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
238a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def _GetSafeServerInstance(self):
239a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''Returns a ServerInstance with a host file system at a safe revision,
240a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    meaning the last revision that the current running version of the server
241a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    existed.
242a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''
243a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    delegate = self._delegate
2444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # IMPORTANT: Get a ServerInstance pinned to the most recent revision, not
2464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # HEAD. These cron jobs take a while and run very frequently such that
2474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # there is usually one running at any given time, and eventually a file
2484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # that we're dealing with will change underneath it, putting the server in
2494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # an undefined state.
2504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    server_instance_near_head = self._CreateServerInstance(
2514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._GetMostRecentRevision())
252a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
253a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    app_yaml_handler = AppYamlHelper(
2544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        server_instance_near_head.object_store_creator,
2554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        server_instance_near_head.host_file_system_provider)
256a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
257a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if app_yaml_handler.IsUpToDate(delegate.GetAppVersion()):
2584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return server_instance_near_head
259a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
260a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    # The version in app.yaml is greater than the currently running app's.
261a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    # The safe version is the one before it changed.
262a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    safe_revision = app_yaml_handler.GetFirstRevisionGreaterThan(
263a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        delegate.GetAppVersion()) - 1
264a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
2653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    _cronlog.info('app version %s is out of date, safe is %s',
2663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        delegate.GetAppVersion(), safe_revision)
267a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
268ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return self._CreateServerInstance(safe_revision)
269a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
2704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def _GetMostRecentRevision(self):
2714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''Gets the revision of the most recent patch submitted to the host file
2724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    system. This is similar to HEAD but it's a concrete revision so won't
2734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    change as the cron runs.
2744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''
2754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    head_fs = (
2764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._CreateServerInstance(None).host_file_system_provider.GetTrunk())
277f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return head_fs.Stat('').version
2784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
279ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def _CreateServerInstance(self, revision):
2804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''Creates a ServerInstance pinned to |revision|, or HEAD if None.
2814e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    NOTE: If passed None it's likely that during the cron run patches will be
2824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    submitted at HEAD, which may change data underneath the cron run.
2834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''
284ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    object_store_creator = ObjectStoreCreator(start_empty=True)
2857dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
2864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    host_file_system_provider = self._delegate.CreateHostFileSystemProvider(
2874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        object_store_creator, max_trunk_revision=revision)
2881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    github_file_system_provider = self._delegate.CreateGithubFileSystemProvider(
289a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        object_store_creator)
290ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return ServerInstance(object_store_creator,
2914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          CompiledFileSystem.Factory(object_store_creator),
2927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                          branch_utility,
2931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                          host_file_system_provider,
2941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                          github_file_system_provider)
295