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, logservice | 9 from appengine_wrappers import DeadlineExceededError, IsDevServer, logservice |
10 from branch_utility import BranchUtility | 10 from branch_utility import BranchUtility |
| 11 from caching_file_system import CachingFileSystem |
| 12 from github_file_system import GithubFileSystem |
| 13 from object_store_creator import ObjectStoreCreator |
11 from render_servlet import RenderServlet | 14 from render_servlet import RenderServlet |
12 from server_instance import ServerInstance | 15 from server_instance import ServerInstance |
13 from servlet import Servlet, Request, Response | 16 from servlet import Servlet, Request, Response |
| 17 from subversion_file_system import SubversionFileSystem |
14 import svn_constants | 18 import svn_constants |
| 19 from third_party.json_schema_compiler.memoize import memoize |
| 20 |
| 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): |
| 36 def __init__(self, server_instance): |
| 37 self._server_instance = server_instance |
| 38 |
| 39 def CreateServerInstanceForChannel(self, channel): |
| 40 return self._server_instance |
15 | 41 |
16 class CronServlet(Servlet): | 42 class CronServlet(Servlet): |
17 '''Servlet which runs a cron job. | 43 '''Servlet which runs a cron job. |
18 ''' | 44 ''' |
| 45 def __init__(self, request, delegate_for_test=None): |
| 46 Servlet.__init__(self, request) |
| 47 self._delegate = delegate_for_test or CronServlet.Delegate() |
| 48 |
| 49 class Delegate(object): |
| 50 '''Allow runtime dependencies to be overridden for testing. |
| 51 ''' |
| 52 def CreateBranchUtility(self, object_store_creator): |
| 53 return BranchUtility.Create(object_store_creator) |
| 54 |
| 55 def CreateHostFileSystemForBranch(self, branch): |
| 56 return SubversionFileSystem.Create(branch) |
| 57 |
| 58 def CreateAppSamplesFileSystem(self, object_store_creator): |
| 59 # TODO(kalman): CachingFileSystem wrapper for GithubFileSystem, but it's |
| 60 # not supported yet (see comment there). |
| 61 return (EmptyDirFileSystem() if IsDevServer() else |
| 62 GithubFileSystem.Create(object_store_creator)) |
| 63 |
19 def Get(self): | 64 def Get(self): |
20 # Crons often time out, and when they do *and* then eventually try to | 65 # Crons often time out, and when they do *and* then eventually try to |
21 # flush logs they die. Turn off autoflush and manually do so at the end. | 66 # flush logs they die. Turn off autoflush and manually do so at the end. |
22 logservice.AUTOFLUSH_ENABLED = False | 67 logservice.AUTOFLUSH_ENABLED = False |
23 try: | 68 try: |
24 return self._GetImpl() | 69 return self._GetImpl() |
25 finally: | 70 finally: |
26 logservice.flush() | 71 logservice.flush() |
27 | 72 |
28 def _GetImpl(self): | 73 def _GetImpl(self): |
29 # Cron strategy: | 74 # Cron strategy: |
30 # | 75 # |
31 # Find all public template files and static files, and render them. Most of | 76 # Find all public template files and static files, and render them. Most of |
32 # the time these won't have changed since the last cron run, so it's a | 77 # the time these won't have changed since the last cron run, so it's a |
33 # little wasteful, but hopefully rendering is really fast (if it isn't we | 78 # little wasteful, but hopefully rendering is really fast (if it isn't we |
34 # have a problem). | 79 # have a problem). |
35 channel = self._request.path.strip('/') | 80 channel = self._request.path.strip('/') |
36 logging.info('cron/%s: starting' % channel) | 81 logging.info('cron/%s: starting' % channel) |
37 | 82 |
38 server_instance = ServerInstance.CreateOnline(channel) | 83 # This is returned every time RenderServlet wants to create a new |
| 84 # ServerInstance. |
| 85 server_instance = _CreateServerInstanceForChannel(channel, self._delegate) |
| 86 |
| 87 def get_via_render_servlet(path): |
| 88 return RenderServlet( |
| 89 Request(path, self._request.host, self._request.headers), |
| 90 _SingletonRenderServletDelegate(server_instance)).Get() |
39 | 91 |
40 def run_cron_for_dir(d, path_prefix=''): | 92 def run_cron_for_dir(d, path_prefix=''): |
41 success = True | 93 success = True |
42 start_time = time.time() | 94 start_time = time.time() |
43 files = [f for f in server_instance.content_cache.GetFromFileListing(d) | 95 files = [f for f in server_instance.content_cache.GetFromFileListing(d) |
44 if not f.endswith('/')] | 96 if not f.endswith('/')] |
45 logging.info('cron/%s: rendering %s files from %s...' % ( | 97 logging.info('cron/%s: rendering %s files from %s...' % ( |
46 channel, len(files), d)) | 98 channel, len(files), d)) |
47 try: | 99 try: |
48 for i, f in enumerate(files): | 100 for i, f in enumerate(files): |
49 error = None | 101 error = None |
50 path = '%s%s' % (path_prefix, f) | 102 path = '%s%s' % (path_prefix, f) |
51 try: | 103 try: |
52 response = RenderServlet(Request(path, self._request.headers)).Get( | 104 response = get_via_render_servlet(path) |
53 server_instance=server_instance) | |
54 if response.status != 200: | 105 if response.status != 200: |
55 error = 'Got %s response' % response.status | 106 error = 'Got %s response' % response.status |
56 except DeadlineExceededError: | 107 except DeadlineExceededError: |
57 logging.error( | 108 logging.error( |
58 'cron/%s: deadline exceeded rendering %s (%s of %s): %s' % ( | 109 'cron/%s: deadline exceeded rendering %s (%s of %s): %s' % ( |
59 channel, path, i + 1, len(files), traceback.format_exc())) | 110 channel, path, i + 1, len(files), traceback.format_exc())) |
60 raise | 111 raise |
61 except error: | 112 except error: |
62 pass | 113 pass |
63 if error: | 114 if error: |
64 logging.error('cron/%s: error rendering %s: %s' % ( | 115 logging.error('cron/%s: error rendering %s: %s' % ( |
65 channel, path, error)) | 116 channel, path, error)) |
66 success = False | 117 success = False |
67 finally: | 118 finally: |
68 logging.info('cron/%s: rendering %s files from %s took %s seconds' % ( | 119 logging.info('cron/%s: rendering %s files from %s took %s seconds' % ( |
69 channel, len(files), d, time.time() - start_time)) | 120 channel, len(files), d, time.time() - start_time)) |
70 return success | 121 return success |
71 | 122 |
72 success = True | 123 success = True |
73 for path, path_prefix in ( | 124 try: |
74 # Note: rendering the public templates will pull in all of the private | 125 # Render all of the publicly accessible files. |
75 # templates. | 126 for path, path_prefix in ( |
76 (svn_constants.PUBLIC_TEMPLATE_PATH, ''), | 127 # Note: rendering the public templates will pull in all of the private |
77 # Note: rendering the public templates will have pulled in the .js and | 128 # templates. |
78 # manifest.json files (for listing examples on the API reference pages), | 129 (svn_constants.PUBLIC_TEMPLATE_PATH, ''), |
79 # but there are still images, CSS, etc. | 130 # Note: rendering the public templates will have pulled in the .js |
80 (svn_constants.STATIC_PATH, 'static/'), | 131 # and manifest.json files (for listing examples on the API reference |
81 (svn_constants.EXAMPLES_PATH, 'extensions/examples/')): | 132 # pages), but there are still images, CSS, etc. |
| 133 (svn_constants.STATIC_PATH, 'static/'), |
| 134 (svn_constants.EXAMPLES_PATH, 'extensions/examples/')): |
| 135 # Note: don't try to short circuit any of this stuff. We want to run |
| 136 # the cron for all the directories regardless of intermediate |
| 137 # failures. |
| 138 success = run_cron_for_dir(path, path_prefix=path_prefix) and success |
| 139 |
| 140 # 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 |
| 142 # runs, too. |
| 143 |
| 144 # 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. |
| 146 manifest_json = '/manifest.json' |
| 147 example_zips = [ |
| 148 '%s.zip' % filename[:-len(manifest_json)] |
| 149 for filename in server_instance.content_cache.GetFromFileListing( |
| 150 svn_constants.EXAMPLES_PATH) |
| 151 if filename.endswith(manifest_json)] |
| 152 logging.info('cron/%s: rendering %s example zips...' % ( |
| 153 channel, len(example_zips))) |
| 154 start_time = time.time() |
82 try: | 155 try: |
83 # Note: don't try to short circuit any of this stuff. We want to run | 156 success = success and all( |
84 # the cron for all the directories regardless of intermediate failures. | 157 get_via_render_servlet('extensions/examples/%s' % z).status == 200 |
85 success = run_cron_for_dir(path, path_prefix=path_prefix) and success | 158 for z in example_zips) |
86 except DeadlineExceededError: | 159 finally: |
87 success = False | 160 logging.info('cron/%s: rendering %s example zips took %s seconds' % ( |
88 break | 161 channel, len(example_zips), time.time() - start_time)) |
| 162 |
| 163 # Also trigger a redirect so that PathCanonicalizer has an opportunity to |
| 164 # cache file listings. |
| 165 logging.info('cron/%s: triggering a redirect...' % channel) |
| 166 redirect_response = get_via_render_servlet('storage.html') |
| 167 success = success and redirect_response.status == 302 |
| 168 except DeadlineExceededError: |
| 169 success = False |
89 | 170 |
90 logging.info('cron/%s: finished' % channel) | 171 logging.info('cron/%s: finished' % channel) |
91 | 172 |
92 return (Response.Ok('Success') if success else | 173 return (Response.Ok('Success') if success else |
93 Response.InternalError('Failure')) | 174 Response.InternalError('Failure')) |
OLD | NEW |