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
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import posixpath
7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import traceback
8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
9a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from app_yaml_helper import AppYamlHelper
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from appengine_wrappers import 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
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from environment import GetAppVersion, IsDevServer
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from extensions_paths import EXAMPLES, PUBLIC_TEMPLATES, STATIC_DOCS
16ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochfrom file_system_util import CreateURLsFromPaths
17effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom future import Future
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from gcs_file_system_provider import CloudStorageFileSystemProvider
191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from github_file_system_provider import GithubFileSystemProvider
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)from host_file_system_provider import HostFileSystemProvider
21b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)from object_store_creator import ObjectStoreCreator
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from render_servlet import RenderServlet
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from server_instance import ServerInstance
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from servlet import Servlet, Request, Response
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from special_paths import SITE_VERIFICATION_FILE
26f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)from timer import Timer, TimerClosure
27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
28b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
29b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)class _SingletonRenderServletDelegate(RenderServlet.Delegate):
30b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  def __init__(self, server_instance):
31b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    self._server_instance = server_instance
32b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
33ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def CreateServerInstance(self):
34b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    return self._server_instance
35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class _CronLogger(object):
373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''Wraps the logging.* methods to prefix them with 'cron' and flush
383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  immediately. The flushing is important because often these cron runs time
393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  out and we lose the logs.
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def info(self, msg, *args):    self._log(logging.info, msg, args)
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def warning(self, msg, *args): self._log(logging.warning, msg, args)
433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def error(self, msg, *args):   self._log(logging.error, msg, args)
443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def _log(self, logfn, msg, args):
463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    try:
473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      logfn('cron: %s' % msg, *args)
483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    finally:
493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      logservice.flush()
503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)_cronlog = _CronLogger()
523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)def _RequestEachItem(title, items, request_callback):
543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''Runs a task |request_callback| named |title| for each item in |items|.
553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  |request_callback| must take an item and return a servlet response.
563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  Returns true if every item was successfully run, false if any return a
573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  non-200 response or raise an exception.
583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''
593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  _cronlog.info('%s: starting', title)
603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  success_count, failure_count = 0, 0
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  timer = Timer()
623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  try:
633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    for i, item in enumerate(items):
643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      def error_message(detail):
653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        return '%s: error rendering %s (%s of %s): %s' % (
663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            title, item, i + 1, len(items), detail)
673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      try:
683551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        response = request_callback(item)
693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        if response.status == 200:
703551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          success_count += 1
713551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        else:
724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          _cronlog.error(error_message('response status %s' % response.status))
733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          failure_count += 1
743551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      except Exception as e:
753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        _cronlog.error(error_message(traceback.format_exc()))
763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        failure_count += 1
773551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        if IsDeadlineExceededError(e): raise
783551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  finally:
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    _cronlog.info('%s: rendered %s of %s with %s failures in %s',
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        title, success_count, len(items), failure_count,
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        timer.Stop().FormatElapsed())
823551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return success_count == len(items)
833551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)class CronServlet(Servlet):
85c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''Servlet which runs a cron job.
86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''
87b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  def __init__(self, request, delegate_for_test=None):
88b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    Servlet.__init__(self, request)
89b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    self._delegate = delegate_for_test or CronServlet.Delegate()
90b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
91b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)  class Delegate(object):
92a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''CronServlet's runtime dependencies. Override for testing.
93b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    '''
94b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    def CreateBranchUtility(self, object_store_creator):
95b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)      return BranchUtility.Create(object_store_creator)
96b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def CreateHostFileSystemProvider(self,
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                     object_store_creator,
994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                     max_trunk_revision=None):
1004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return HostFileSystemProvider(object_store_creator,
1014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                    max_trunk_revision=max_trunk_revision)
102b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1031e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    def CreateGithubFileSystemProvider(self, object_store_creator):
1041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      return GithubFileSystemProvider(object_store_creator)
105b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def CreateGCSFileSystemProvider(self, object_store_creator):
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return CloudStorageFileSystemProvider(object_store_creator)
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
109a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    def GetAppVersion(self):
110a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return GetAppVersion()
111a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
112c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def Get(self):
1133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # Crons often time out, and if they do we need to make sure to flush the
1143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # logs before the process gets killed (Python gives us a couple of
1153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # seconds).
1163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    #
1173551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # So, manually flush logs at the end of the cron run. However, sometimes
1183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # even that isn't enough, which is why in this file we use _cronlog and
1193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # make it flush the log every time its used.
120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    logservice.AUTOFLUSH_ENABLED = False
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self._GetImpl()
1233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    except BaseException:
1243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      _cronlog.error('Caught top-level exception! %s', traceback.format_exc())
125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    finally:
126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      logservice.flush()
127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def _GetImpl(self):
129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Cron strategy:
130c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    #
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Find all public template files and static files, and render them. Most of
132c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # the time these won't have changed since the last cron run, so it's a
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # little wasteful, but hopefully rendering is really fast (if it isn't we
134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # have a problem).
1353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    _cronlog.info('starting')
136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
137b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    # This is returned every time RenderServlet wants to create a new
138b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    # ServerInstance.
1393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    #
1403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # TODO(kalman): IMPORTANT. This sometimes throws an exception, breaking
1413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # everything. Need retry logic at the fetcher level.
142a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    server_instance = self._GetSafeServerInstance()
1434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    trunk_fs = server_instance.host_file_system_provider.GetTrunk()
144b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    def render(path):
146eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      request = Request(path, self._request.host, self._request.headers)
147eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      delegate = _SingletonRenderServletDelegate(server_instance)
148eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return RenderServlet(request, delegate).Get()
149c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def request_files_in_dir(path, prefix='', strip_ext=None):
1513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      '''Requests every file found under |path| in this host file system, with
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      a request prefix of |prefix|. |strip_ext| is an optional list of file
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extensions that should be stripped from paths before requesting.
1543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      '''
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      def maybe_strip_ext(name):
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if name == SITE_VERIFICATION_FILE or not strip_ext:
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          return name
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        base, ext = posixpath.splitext(name)
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return base if ext in strip_ext else name
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      files = [maybe_strip_ext(name)
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               for name, _ in CreateURLsFromPaths(trunk_fs, path, prefix)]
1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return _RequestEachItem(path, files, render)
1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    results = []
165a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
1663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    try:
167f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Start running the hand-written Cron methods first; they can be run in
168f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # parallel. They are resolved at the end.
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      def run_cron_for_future(target):
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        title = target.__class__.__name__
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        future, init_timer = TimerClosure(target.Cron)
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        assert isinstance(future, Future), (
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '%s.Cron() did not return a Future' % title)
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        def resolve():
175f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          resolve_timer = Timer()
176f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          try:
177f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            future.Get()
178f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          except Exception as e:
179f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            _cronlog.error('%s: error %s' % (title, traceback.format_exc()))
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            results.append(False)
181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if IsDeadlineExceededError(e): raise
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          finally:
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            resolve_timer.Stop()
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            _cronlog.info('%s took %s: %s to initialize and %s to resolve' %
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                (title,
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 init_timer.With(resolve_timer).FormatElapsed(),
187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 init_timer.FormatElapsed(),
188f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                 resolve_timer.FormatElapsed()))
189effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        return Future(callback=resolve)
190f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
191f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      targets = (CreateDataSources(server_instance).values() +
1920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                 [server_instance.content_providers,
1930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                  server_instance.api_models])
194f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      title = 'initializing %s parallel Cron targets' % len(targets)
195f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      _cronlog.info(title)
196f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      timer = Timer()
197f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
198f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        cron_futures = [run_cron_for_future(target) for target in targets]
199f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      finally:
200f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        _cronlog.info('%s took %s' % (title, timer.Stop().FormatElapsed()))
201f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
2023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # Samples are too expensive to run on the dev server, where there is no
2033551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # parallel fetch.
204effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      #
205effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      # XXX(kalman): Currently samples are *always* too expensive to fetch, so
206effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      # disabling them for now. It won't break anything so long as we're still
207effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      # not enforcing that everything gets cached for normal instances.
208effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      if False:  # should be "not IsDevServer()":
2093551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        # Fetch each individual sample file.
210f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        results.append(request_files_in_dir(EXAMPLES,
211f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                            prefix='extensions/examples'))
2123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
213f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Resolve the hand-written Cron method futures.
214f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      title = 'resolving %s parallel Cron targets' % len(targets)
215f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      _cronlog.info(title)
216f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      timer = Timer()
217f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
218f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        for future in cron_futures:
219f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          future.Get()
220f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      finally:
221f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        _cronlog.info('%s took %s' % (title, timer.Stop().FormatElapsed()))
222eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
223d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    except:
2243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      results.append(False)
2253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # This should never actually happen (each cron step does its own
2263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      # conservative error checking), so re-raise no matter what it is.
227d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      _cronlog.error('uncaught error: %s' % traceback.format_exc())
2283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      raise
2293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    finally:
2303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      success = all(results)
2313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      _cronlog.info('finished (%s)', 'success' if success else 'FAILED')
2323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return (Response.Ok('Success') if success else
2333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)              Response.InternalError('Failure'))
234a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
235a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def _GetSafeServerInstance(self):
236a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''Returns a ServerInstance with a host file system at a safe revision,
237a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    meaning the last revision that the current running version of the server
238a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    existed.
239a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    '''
240a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    delegate = self._delegate
2414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # IMPORTANT: Get a ServerInstance pinned to the most recent revision, not
2434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # HEAD. These cron jobs take a while and run very frequently such that
2444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # there is usually one running at any given time, and eventually a file
2454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # that we're dealing with will change underneath it, putting the server in
2464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # an undefined state.
2474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    server_instance_near_head = self._CreateServerInstance(
2484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._GetMostRecentRevision())
249a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
250a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    app_yaml_handler = AppYamlHelper(
2514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        server_instance_near_head.object_store_creator,
2524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        server_instance_near_head.host_file_system_provider)
253a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
254a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if app_yaml_handler.IsUpToDate(delegate.GetAppVersion()):
2554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return server_instance_near_head
256a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
257a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    # The version in app.yaml is greater than the currently running app's.
258a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    # The safe version is the one before it changed.
259a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    safe_revision = app_yaml_handler.GetFirstRevisionGreaterThan(
260a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        delegate.GetAppVersion()) - 1
261a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
2623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    _cronlog.info('app version %s is out of date, safe is %s',
2633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        delegate.GetAppVersion(), safe_revision)
264a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
265ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return self._CreateServerInstance(safe_revision)
266a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
2674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def _GetMostRecentRevision(self):
2684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''Gets the revision of the most recent patch submitted to the host file
2694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    system. This is similar to HEAD but it's a concrete revision so won't
2704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    change as the cron runs.
2714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''
2724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    head_fs = (
2734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._CreateServerInstance(None).host_file_system_provider.GetTrunk())
274f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return head_fs.Stat('').version
2754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
276ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def _CreateServerInstance(self, revision):
2774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''Creates a ServerInstance pinned to |revision|, or HEAD if None.
2784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    NOTE: If passed None it's likely that during the cron run patches will be
2794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    submitted at HEAD, which may change data underneath the cron run.
2804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''
281ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    object_store_creator = ObjectStoreCreator(start_empty=True)
2827dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
2834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    host_file_system_provider = self._delegate.CreateHostFileSystemProvider(
2844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        object_store_creator, max_trunk_revision=revision)
2851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    github_file_system_provider = self._delegate.CreateGithubFileSystemProvider(
286a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        object_store_creator)
2875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    gcs_file_system_provider = self._delegate.CreateGCSFileSystemProvider(
2885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        object_store_creator)
289ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return ServerInstance(object_store_creator,
2904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          CompiledFileSystem.Factory(object_store_creator),
2917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                          branch_utility,
2921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                          host_file_system_provider,
2935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          github_file_system_provider,
2945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                          gcs_file_system_provider)
295