1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
8 from app_yaml_helper import AppYamlHelper
9 from appengine_wrappers import IsDeadlineExceededError, logservice, taskqueue
10 from branch_utility import BranchUtility
11 from compiled_file_system import CompiledFileSystem
12 from custom_logger import CustomLogger
13 from data_source_registry import CreateDataSources
14 from environment import GetAppVersion
15 from gcs_file_system_provider import CloudStorageFileSystemProvider
16 from github_file_system_provider import GithubFileSystemProvider
17 from host_file_system_provider import HostFileSystemProvider
18 from object_store_creator import ObjectStoreCreator
19 from render_refresher import RenderRefresher
20 from server_instance import ServerInstance
21 from servlet import Servlet, Request, Response
22 from timer import Timer
25 _log = CustomLogger('cron')
28 class CronServlet(Servlet):
29 '''Servlet which runs a cron job.
31 def __init__(self, request, delegate_for_test=None):
32 Servlet.__init__(self, request)
33 self._delegate = delegate_for_test or CronServlet.Delegate()
35 class Delegate(object):
36 '''CronServlet's runtime dependencies. Override for testing.
38 def CreateBranchUtility(self, object_store_creator):
39 return BranchUtility.Create(object_store_creator)
41 def CreateHostFileSystemProvider(self,
44 return HostFileSystemProvider(object_store_creator,
45 pinned_commit=pinned_commit)
47 def CreateGithubFileSystemProvider(self, object_store_creator):
48 return GithubFileSystemProvider(object_store_creator)
50 def CreateGCSFileSystemProvider(self, object_store_creator):
51 return CloudStorageFileSystemProvider(object_store_creator)
53 def GetAppVersion(self):
54 return GetAppVersion()
57 # Refreshes may time out, and if they do we need to make sure to flush the
58 # logs before the process gets killed (Python gives us a couple of
61 # So, manually flush logs at the end of the cron run. However, sometimes
62 # even that isn't enough, which is why in this file we use _log and
63 # make it flush the log every time its used.
64 logservice.AUTOFLUSH_ENABLED = False
66 return self._GetImpl()
68 _log.error('Caught top-level exception! %s', traceback.format_exc())
75 # Collect all DataSources, the PlatformBundle, the ContentProviders, and
76 # any other statically renderered contents (e.g. examples content),
77 # and spin up taskqueue tasks which will refresh any cached data relevant
80 # TODO(rockot/kalman): At the moment examples are not actually refreshed
81 # because they're too slow.
85 server_instance = self._GetSafeServerInstance()
86 master_fs = server_instance.host_file_system_provider.GetMaster()
87 master_commit = master_fs.GetCommitID().Get()
89 # This is the guy that would be responsible for refreshing the cache of
90 # examples. Here for posterity, hopefully it will be added to the targets
92 render_refresher = RenderRefresher(server_instance, self._request)
94 # Get the default taskqueue
95 queue = taskqueue.Queue()
97 # GAE documentation specifies that it's bad to add tasks to a queue
98 # within one second of purging. We wait 2 seconds, because we like
99 # to go the extra mile.
105 data_sources = CreateDataSources(server_instance)
106 targets = (data_sources.items() +
107 [('content_providers', server_instance.content_providers),
108 ('platform_bundle', server_instance.platform_bundle)])
109 title = 'initializing %s parallel targets' % len(targets)
112 for name, target in targets:
113 refresh_paths = target.GetRefreshPaths()
114 for path in refresh_paths:
115 queue.add(taskqueue.Task(url='/_refresh/%s/%s' % (name, path),
116 params={'commit': master_commit}))
117 _log.info('%s took %s' % (title, timer.Stop().FormatElapsed()))
119 # This should never actually happen (each cron step does its own
120 # conservative error checking), so re-raise no matter what it is.
121 _log.error('uncaught error: %s' % traceback.format_exc())
125 _log.info('finished (%s)', 'success' if success else 'FAILED')
126 return (Response.Ok('Success') if success else
127 Response.InternalError('Failure'))
129 def _GetSafeServerInstance(self):
130 '''Returns a ServerInstance with a host file system at a safe commit,
131 meaning the last commit that the current running version of the server
134 delegate = self._delegate
136 # IMPORTANT: Get a ServerInstance pinned to the most recent commit, not
137 # HEAD. These cron jobs take a while and run very frequently such that
138 # there is usually one running at any given time, and eventually a file
139 # that we're dealing with will change underneath it, putting the server in
140 # an undefined state.
141 server_instance_near_head = self._CreateServerInstance(
142 self._GetMostRecentCommit())
144 app_yaml_handler = AppYamlHelper(
145 server_instance_near_head.object_store_creator,
146 server_instance_near_head.host_file_system_provider)
148 if app_yaml_handler.IsUpToDate(delegate.GetAppVersion()):
149 return server_instance_near_head
151 # The version in app.yaml is greater than the currently running app's.
152 # The safe version is the one before it changed.
153 safe_revision = app_yaml_handler.GetFirstRevisionGreaterThan(
154 delegate.GetAppVersion()) - 1
156 _log.info('app version %s is out of date, safe is %s',
157 delegate.GetAppVersion(), safe_revision)
159 return self._CreateServerInstance(safe_revision)
161 def _GetMostRecentCommit(self):
162 '''Gets the commit of the most recent patch submitted to the host file
163 system. This is similar to HEAD but it's a concrete commit so won't
164 change as the cron runs.
167 self._CreateServerInstance(None).host_file_system_provider.GetMaster())
168 return head_fs.GetCommitID().Get()
170 def _CreateServerInstance(self, commit):
171 '''Creates a ServerInstance pinned to |commit|, or HEAD if None.
172 NOTE: If passed None it's likely that during the cron run patches will be
173 submitted at HEAD, which may change data underneath the cron run.
175 object_store_creator = ObjectStoreCreator(start_empty=True)
176 branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
177 host_file_system_provider = self._delegate.CreateHostFileSystemProvider(
178 object_store_creator, pinned_commit=commit)
179 github_file_system_provider = self._delegate.CreateGithubFileSystemProvider(
180 object_store_creator)
181 gcs_file_system_provider = self._delegate.CreateGCSFileSystemProvider(
182 object_store_creator)
183 return ServerInstance(object_store_creator,
184 CompiledFileSystem.Factory(object_store_creator),
186 host_file_system_provider,
187 github_file_system_provider,
188 gcs_file_system_provider)