Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 time | 6 import time |
| 7 import traceback | 7 import traceback |
| 8 | 8 |
| 9 from appengine_wrappers import DeadlineExceededError, IsDevServer, logservice | 9 from app_yaml_helper import AppYamlHelper |
| 10 from appengine_wrappers import ( | |
| 11 GetAppVersion, DeadlineExceededError, IsDevServer, logservice) | |
| 10 from branch_utility import BranchUtility | 12 from branch_utility import BranchUtility |
| 11 from caching_file_system import CachingFileSystem | 13 from caching_file_system import CachingFileSystem |
| 14 from empty_dir_file_system import EmptyDirFileSystem | |
| 12 from github_file_system import GithubFileSystem | 15 from github_file_system import GithubFileSystem |
| 13 from object_store_creator import ObjectStoreCreator | 16 from object_store_creator import ObjectStoreCreator |
| 14 from render_servlet import RenderServlet | 17 from render_servlet import RenderServlet |
| 15 from server_instance import ServerInstance | 18 from server_instance import ServerInstance |
| 16 from servlet import Servlet, Request, Response | 19 from servlet import Servlet, Request, Response |
| 17 from subversion_file_system import SubversionFileSystem | 20 from subversion_file_system import SubversionFileSystem |
| 18 import svn_constants | 21 import svn_constants |
| 19 from third_party.json_schema_compiler.memoize import memoize | 22 from third_party.json_schema_compiler.memoize import memoize |
| 20 | 23 |
| 21 def _CreateServerInstanceForChannel(channel, delegate): | |
| 22 object_store_creator = ObjectStoreCreator(channel, start_empty=True) | |
| 23 branch = (delegate.CreateBranchUtility(object_store_creator) | |
| 24 .GetBranchForChannel(channel)) | |
| 25 host_file_system = CachingFileSystem( | |
| 26 delegate.CreateHostFileSystemForBranch(branch), | |
| 27 object_store_creator) | |
| 28 app_samples_file_system = delegate.CreateAppSamplesFileSystem( | |
| 29 object_store_creator) | |
| 30 return ServerInstance(channel, | |
| 31 object_store_creator, | |
| 32 host_file_system, | |
| 33 app_samples_file_system) | |
| 34 | |
| 35 class _SingletonRenderServletDelegate(RenderServlet.Delegate): | 24 class _SingletonRenderServletDelegate(RenderServlet.Delegate): |
| 36 def __init__(self, server_instance): | 25 def __init__(self, server_instance): |
| 37 self._server_instance = server_instance | 26 self._server_instance = server_instance |
| 38 | 27 |
| 39 def CreateServerInstanceForChannel(self, channel): | 28 def CreateServerInstanceForChannel(self, channel): |
| 40 return self._server_instance | 29 return self._server_instance |
| 41 | 30 |
| 42 class CronServlet(Servlet): | 31 class CronServlet(Servlet): |
| 43 '''Servlet which runs a cron job. | 32 '''Servlet which runs a cron job. |
| 44 ''' | 33 ''' |
| 45 def __init__(self, request, delegate_for_test=None): | 34 def __init__(self, request, delegate_for_test=None): |
| 46 Servlet.__init__(self, request) | 35 Servlet.__init__(self, request) |
| 36 self._channel = request.path.strip('/') | |
| 47 self._delegate = delegate_for_test or CronServlet.Delegate() | 37 self._delegate = delegate_for_test or CronServlet.Delegate() |
| 48 | 38 |
| 49 class Delegate(object): | 39 class Delegate(object): |
| 50 '''Allow runtime dependencies to be overridden for testing. | 40 '''CronServlet's runtime dependencies. Override for testing. |
| 51 ''' | 41 ''' |
| 52 def CreateBranchUtility(self, object_store_creator): | 42 def CreateBranchUtility(self, object_store_creator): |
| 53 return BranchUtility.Create(object_store_creator) | 43 return BranchUtility.Create(object_store_creator) |
| 54 | 44 |
| 55 def CreateHostFileSystemForBranch(self, branch): | 45 def CreateHostFileSystemForBranchAndRevision(self, branch, revision): |
| 56 return SubversionFileSystem.Create(branch) | 46 return SubversionFileSystem.Create(branch, revision=revision) |
| 57 | 47 |
| 58 def CreateAppSamplesFileSystem(self, object_store_creator): | 48 def CreateAppSamplesFileSystem(self, object_store_creator): |
| 59 # TODO(kalman): CachingFileSystem wrapper for GithubFileSystem, but it's | 49 # TODO(kalman): CachingFileSystem wrapper for GithubFileSystem, but it's |
| 60 # not supported yet (see comment there). | 50 # not supported yet (see comment there). |
| 61 return (EmptyDirFileSystem() if IsDevServer() else | 51 return (EmptyDirFileSystem() if IsDevServer() else |
| 62 GithubFileSystem.Create(object_store_creator)) | 52 GithubFileSystem.Create(object_store_creator)) |
| 63 | 53 |
| 54 def GetAppVersion(self): | |
| 55 return GetAppVersion() | |
| 56 | |
| 64 def Get(self): | 57 def Get(self): |
| 65 # Crons often time out, and when they do *and* then eventually try to | 58 # Crons often time out, and when they do *and* then eventually try to |
| 66 # flush logs they die. Turn off autoflush and manually do so at the end. | 59 # flush logs they die. Turn off autoflush and manually do so at the end. |
| 67 logservice.AUTOFLUSH_ENABLED = False | 60 logservice.AUTOFLUSH_ENABLED = False |
| 68 try: | 61 try: |
| 69 return self._GetImpl() | 62 return self._GetImpl() |
| 70 finally: | 63 finally: |
| 71 logservice.flush() | 64 logservice.flush() |
| 72 | 65 |
| 73 def _GetImpl(self): | 66 def _GetImpl(self): |
| 74 # Cron strategy: | 67 # Cron strategy: |
| 75 # | 68 # |
| 76 # Find all public template files and static files, and render them. Most of | 69 # Find all public template files and static files, and render them. Most of |
| 77 # the time these won't have changed since the last cron run, so it's a | 70 # the time these won't have changed since the last cron run, so it's a |
| 78 # little wasteful, but hopefully rendering is really fast (if it isn't we | 71 # little wasteful, but hopefully rendering is really fast (if it isn't we |
| 79 # have a problem). | 72 # have a problem). |
| 80 channel = self._request.path.strip('/') | 73 channel = self._channel |
| 81 logging.info('cron/%s: starting' % channel) | 74 logging.info('cron/%s: starting' % channel) |
| 82 | 75 |
| 83 # This is returned every time RenderServlet wants to create a new | 76 # This is returned every time RenderServlet wants to create a new |
| 84 # ServerInstance. | 77 # ServerInstance. |
| 85 server_instance = _CreateServerInstanceForChannel(channel, self._delegate) | 78 server_instance = self._GetSafeServerInstance() |
| 86 | 79 |
| 87 def get_via_render_servlet(path): | 80 def get_via_render_servlet(path): |
| 88 return RenderServlet( | 81 return RenderServlet( |
| 89 Request(path, self._request.host, self._request.headers), | 82 Request(path, self._request.host, self._request.headers), |
| 90 _SingletonRenderServletDelegate(server_instance)).Get() | 83 _SingletonRenderServletDelegate(server_instance)).Get() |
| 91 | 84 |
| 92 def run_cron_for_dir(d, path_prefix=''): | 85 def run_cron_for_dir(d, path_prefix=''): |
| 93 success = True | 86 success = True |
| 94 start_time = time.time() | 87 start_time = time.time() |
| 95 files = [f for f in server_instance.content_cache.GetFromFileListing(d) | 88 files = [f for f in server_instance.content_cache.GetFromFileListing(d) |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 116 channel, path, error)) | 109 channel, path, error)) |
| 117 success = False | 110 success = False |
| 118 finally: | 111 finally: |
| 119 logging.info('cron/%s: rendering %s files from %s took %s seconds' % ( | 112 logging.info('cron/%s: rendering %s files from %s took %s seconds' % ( |
| 120 channel, len(files), d, time.time() - start_time)) | 113 channel, len(files), d, time.time() - start_time)) |
| 121 return success | 114 return success |
| 122 | 115 |
| 123 success = True | 116 success = True |
| 124 try: | 117 try: |
| 125 # Render all of the publicly accessible files. | 118 # Render all of the publicly accessible files. |
| 126 for path, path_prefix in ( | 119 cron_runs = [ |
| 127 # Note: rendering the public templates will pull in all of the private | 120 # Note: rendering the public templates will pull in all of the private |
| 128 # templates. | 121 # templates. |
| 129 (svn_constants.PUBLIC_TEMPLATE_PATH, ''), | 122 (svn_constants.PUBLIC_TEMPLATE_PATH, ''), |
| 130 # Note: rendering the public templates will have pulled in the .js | 123 # Note: rendering the public templates will have pulled in the .js |
| 131 # and manifest.json files (for listing examples on the API reference | 124 # and manifest.json files (for listing examples on the API reference |
| 132 # pages), but there are still images, CSS, etc. | 125 # pages), but there are still images, CSS, etc. |
| 133 (svn_constants.STATIC_PATH, 'static/'), | 126 (svn_constants.STATIC_PATH, 'static/'), |
| 134 (svn_constants.EXAMPLES_PATH, 'extensions/examples/')): | 127 ] |
| 135 # Note: don't try to short circuit any of this stuff. We want to run | 128 if not IsDevServer(): |
| 136 # the cron for all the directories regardless of intermediate | 129 cron_runs.append( |
| 137 # failures. | 130 (svn_constants.EXAMPLES_PATH, 'extensions/examples/')) |
| 131 | |
| 132 # Note: don't try to short circuit any of this stuff. We want to run | |
| 133 # the cron for all the directories regardless of intermediate | |
| 134 # failures. | |
| 135 for path, path_prefix in cron_runs: | |
| 138 success = run_cron_for_dir(path, path_prefix=path_prefix) and success | 136 success = run_cron_for_dir(path, path_prefix=path_prefix) and success |
| 139 | 137 |
| 140 # TODO(kalman): Generic way for classes to request cron access. The next | 138 # TODO(kalman): Generic way for classes to request cron access. The next |
| 141 # two special cases are ugly. It would potentially greatly speed up cron | 139 # two special cases are ugly. It would potentially greatly speed up cron |
| 142 # runs, too. | 140 # runs, too. |
| 143 | 141 |
| 144 # Extension examples have zip files too. Well, so do apps, but the app | 142 # Extension examples have zip files too. Well, so do apps, but the app |
| 145 # file system doesn't get the Offline treatment so they don't need cron. | 143 # file system doesn't get the Offline treatment so they don't need cron. |
| 146 manifest_json = '/manifest.json' | 144 if not IsDevServer(): |
| 147 example_zips = [ | 145 manifest_json = '/manifest.json' |
| 148 '%s.zip' % filename[:-len(manifest_json)] | 146 example_zips = [ |
| 149 for filename in server_instance.content_cache.GetFromFileListing( | 147 '%s.zip' % filename[:-len(manifest_json)] |
| 150 svn_constants.EXAMPLES_PATH) | 148 for filename in server_instance.content_cache.GetFromFileListing( |
| 151 if filename.endswith(manifest_json)] | 149 svn_constants.EXAMPLES_PATH) |
| 152 logging.info('cron/%s: rendering %s example zips...' % ( | 150 if filename.endswith(manifest_json)] |
| 153 channel, len(example_zips))) | 151 logging.info('cron/%s: rendering %s example zips...' % ( |
| 154 start_time = time.time() | 152 channel, len(example_zips))) |
| 155 try: | 153 start_time = time.time() |
| 156 success = success and all( | 154 try: |
| 157 get_via_render_servlet('extensions/examples/%s' % z).status == 200 | 155 success = success and all( |
| 158 for z in example_zips) | 156 get_via_render_servlet('extensions/examples/%s' % z).status == 200 |
| 159 finally: | 157 for z in example_zips) |
| 160 logging.info('cron/%s: rendering %s example zips took %s seconds' % ( | 158 finally: |
| 161 channel, len(example_zips), time.time() - start_time)) | 159 logging.info('cron/%s: rendering %s example zips took %s seconds' % ( |
| 160 channel, len(example_zips), time.time() - start_time)) | |
| 162 | 161 |
| 163 # Also trigger a redirect so that PathCanonicalizer has an opportunity to | 162 # Also trigger a redirect so that PathCanonicalizer has an opportunity to |
| 164 # cache file listings. | 163 # cache file listings. |
| 165 logging.info('cron/%s: triggering a redirect...' % channel) | 164 logging.info('cron/%s: triggering a redirect...' % channel) |
| 166 redirect_response = get_via_render_servlet('storage.html') | 165 redirect_response = get_via_render_servlet('storage.html') |
| 167 success = success and redirect_response.status == 302 | 166 success = success and redirect_response.status == 302 |
| 168 except DeadlineExceededError: | 167 except DeadlineExceededError: |
| 169 success = False | 168 success = False |
| 170 | 169 |
| 171 logging.info('cron/%s: finished' % channel) | 170 logging.info('cron/%s: finished' % channel) |
| 172 | 171 |
| 173 return (Response.Ok('Success') if success else | 172 return (Response.Ok('Success') if success else |
| 174 Response.InternalError('Failure')) | 173 Response.InternalError('Failure')) |
| 174 | |
| 175 def _GetSafeServerInstance(self): | |
| 176 '''Returns a ServerInstance with a host file system at a safe revision, | |
| 177 meaning the last revision that the current running version of the server | |
| 178 existed. | |
| 179 ''' | |
| 180 channel = self._channel | |
| 181 delegate = self._delegate | |
| 182 | |
| 183 server_instance_at_head = self._CreateServerInstance(channel, None) | |
| 184 | |
| 185 get_branch_for_channel = self._GetBranchForChannel | |
|
cduvall
2013/05/10 06:45:19
is this line necessary? does it not like you using
not at google - send to devlin
2013/05/10 17:49:58
Yeah, I either do this or save a reference to Cron
| |
| 186 class AppYamlHelperDelegate(AppYamlHelper.Delegate): | |
| 187 def GetHostFileSystemForRevision(self, revision): | |
| 188 return delegate.CreateHostFileSystemForBranchAndRevision( | |
| 189 get_branch_for_channel(channel), | |
| 190 revision) | |
| 191 | |
| 192 app_yaml_handler = AppYamlHelper( | |
| 193 svn_constants.APP_YAML_PATH, | |
| 194 server_instance_at_head.host_file_system, | |
| 195 AppYamlHelperDelegate(), | |
| 196 server_instance_at_head.object_store_creator) | |
| 197 | |
| 198 if app_yaml_handler.IsUpToDate(delegate.GetAppVersion()): | |
| 199 return server_instance_at_head | |
|
方觉(Fang Jue)
2013/05/10 08:49:17
I was wondering what will happen if at this point
not at google - send to devlin
2013/05/10 17:49:58
Yes true. I have a bug filed about this. There's a
| |
| 200 | |
| 201 # The version in app.yaml is greater than the currently running app's. | |
| 202 # The safe version is the one before it changed. | |
| 203 safe_revision = app_yaml_handler.GetFirstRevisionGreaterThan( | |
| 204 delegate.GetAppVersion()) - 1 | |
| 205 | |
| 206 logging.info('cron/%s: app version %s is out of date, safe is %s' % ( | |
| 207 channel, delegate.GetAppVersion(), safe_revision)) | |
| 208 | |
| 209 return self._CreateServerInstance(channel, safe_revision) | |
| 210 | |
| 211 def _CreateObjectStoreCreator(self, channel): | |
| 212 return ObjectStoreCreator(channel, start_empty=True) | |
| 213 | |
| 214 def _GetBranchForChannel(self, channel): | |
| 215 object_store_creator = self._CreateObjectStoreCreator(channel) | |
| 216 return (self._delegate.CreateBranchUtility(object_store_creator) | |
| 217 .GetBranchForChannel(channel)) | |
| 218 | |
| 219 def _CreateServerInstance(self, channel, revision): | |
| 220 object_store_creator = self._CreateObjectStoreCreator(channel) | |
| 221 host_file_system = CachingFileSystem( | |
| 222 self._delegate.CreateHostFileSystemForBranchAndRevision( | |
| 223 self._GetBranchForChannel(channel), | |
| 224 revision), | |
| 225 object_store_creator) | |
| 226 app_samples_file_system = self._delegate.CreateAppSamplesFileSystem( | |
| 227 object_store_creator) | |
| 228 return ServerInstance(channel, | |
| 229 object_store_creator, | |
| 230 host_file_system, | |
| 231 app_samples_file_system) | |
| OLD | NEW |