Major files refactoring - isolate.

- Split main from handlers declaration for endpoints handlers.
- Remove unnecessary imports.
- Remove APP from mapreduce_jobs, it unnecessary.
- Rename source files for consistency.
- Enforce debug parameter on all create_application().
- Remove appstats, the new Cloud Console Trace features can give meaningful data
  without require intervention from within the app.

R=vadimsh@chromium.org
BUG=

Review URL: https://codereview.appspot.com/221150043
diff --git a/appengine/isolate/app.yaml b/appengine/isolate/app.yaml
index 7bf3fa6..d40689e 100644
--- a/appengine/isolate/app.yaml
+++ b/appengine/isolate/app.yaml
@@ -13,16 +13,16 @@
 
 handlers:
 - url: /_ah/spi/.*
-  script: endpoint_handlers_api.app
+  script: main_frontend.endpoint_app
   secure: always
 
 - url: /restricted/mapreduce(/.*)?
-  script: mapreduce_jobs.app
+  script: mapreduce.main.APP
   secure: always
   login: admin
 
 - url: /restricted.*
-  script: main_frontend.app
+  script: main_frontend.frontend_app
   secure: always
   login: required
 
@@ -44,11 +44,10 @@
     X-Frame-Options: deny
 
 - url: /.*
-  script: main_frontend.app
+  script: main_frontend.frontend_app
   secure: always
 
 builtins:
-- appstats: on
 - remote_api: on
 
 inbound_services:
diff --git a/appengine/isolate/appengine_config.py b/appengine/isolate/appengine_config.py
index 4cdbb4c..a4ce9c8 100644
--- a/appengine/isolate/appengine_config.py
+++ b/appengine/isolate/appengine_config.py
@@ -2,10 +2,9 @@
 # Use of this source code is governed by the Apache v2.0 license that can be
 # found in the LICENSE file.
 
-"""Configures includes (appstats and components.auth).
+"""Configures includes (components.auth).
 
 https://developers.google.com/appengine/docs/python/tools/appengineconfig
 """
 
-appstats_CALC_RPC_COSTS = False
 components_auth_UI_APP_NAME = 'Isolate Server'
diff --git a/appengine/isolate/handlers_backend.py b/appengine/isolate/handlers_backend.py
index a7deb25..34c045e 100644
--- a/appengine/isolate/handlers_backend.py
+++ b/appengine/isolate/handlers_backend.py
@@ -479,7 +479,7 @@
   ]
 
 
-def create_application(debug=False):
+def create_application(debug):
   """Creates the url router for the backend.
 
   The backend only implements urls under /internal/.
diff --git a/appengine/isolate/endpoint_handlers_api.py b/appengine/isolate/handlers_endpoints.py
similarity index 96%
rename from appengine/isolate/endpoint_handlers_api.py
rename to appengine/isolate/handlers_endpoints.py
index f749af4..860fc65 100644
--- a/appengine/isolate/endpoint_handlers_api.py
+++ b/appengine/isolate/handlers_endpoints.py
@@ -6,17 +6,14 @@
 
 import binascii
 import datetime
-import hashlib
-import hmac
-import httplib
-import os
 import re
-import sys
 import time
-import urllib
 
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-sys.path.insert(0, os.path.join(BASE_DIR, 'third_party'))
+from google.appengine import runtime
+from google.appengine.api import datastore_errors
+from google.appengine.api import memcache
+from google.appengine.api import taskqueue
+from google.appengine.ext import ndb
 
 import endpoints
 from protorpc import message_types
@@ -24,22 +21,15 @@
 from protorpc import remote
 
 from components import auth
-from components import ereporter2
 from components import utils
+
 import config
 import gcs
 from handlers_api import hash_content
 from handlers_api import MIN_SIZE_FOR_DIRECT_GS
-from handlers_api import MIN_SIZE_FOR_GS
 import model
 import stats
 
-from google.appengine import runtime
-from google.appengine.api import datastore_errors
-from google.appengine.api import memcache
-from google.appengine.api import taskqueue
-from google.appengine.ext import ndb
-
 
 ### Request Types
 
@@ -462,11 +452,3 @@
       payload = ''.join(
           binascii.unhexlify(digest.digest) for digest in collection.items)
       return utils.enqueue_task(url, 'tag', payload=payload)
-
-
-def create_application():
-  ereporter2.register_formatter()
-  return endpoints.api_server([IsolateService])
-
-
-app = create_application()
diff --git a/appengine/isolate/endpoint_handlers_api_test.py b/appengine/isolate/handlers_endpoints_test.py
similarity index 85%
rename from appengine/isolate/endpoint_handlers_api_test.py
rename to appengine/isolate/handlers_endpoints_test.py
index ef560d3..eb56872 100755
--- a/appengine/isolate/endpoint_handlers_api_test.py
+++ b/appengine/isolate/handlers_endpoints_test.py
@@ -6,8 +6,6 @@
 import base64
 import json
 import logging
-import os
-import re
 import sys
 import unittest
 from Crypto.PublicKey import RSA
@@ -17,9 +15,7 @@
 
 from google.appengine.api import memcache
 from google.appengine.api import taskqueue
-from google.appengine.api import urlfetch
 
-import endpoints
 from protorpc.remote import protojson
 import webapp2
 import webtest
@@ -29,11 +25,9 @@
 from test_support import test_case
 
 import config
-import endpoint_handlers_api
-from endpoint_handlers_api import DigestCollection
-from endpoint_handlers_api import UPLOAD_MESSAGES
 import gcs
 import handlers_backend
+import handlers_endpoints
 import model
 
 
@@ -60,14 +54,14 @@
   Returns:
     a Digest corresponding to the content/ namespace pair
   """
-  return endpoint_handlers_api.Digest(
+  return handlers_endpoints.Digest(
       digest=hash_content(content, namespace), size=len(content))
 
 
 def generate_collection(contents, namespace=None):
   if namespace is None:
-    namespace = endpoint_handlers_api.Namespace()
-  return DigestCollection(
+    namespace = handlers_endpoints.Namespace()
+  return handlers_endpoints.DigestCollection(
       namespace=namespace,
       items=[generate_digest(content, namespace.namespace)
              for content in contents])
@@ -89,15 +83,15 @@
   ticket = preupload_status.get('upload_ticket')
   url = preupload_status.get('gs_upload_url', None)
   if url is not None:
-    return endpoint_handlers_api.FinalizeRequest(upload_ticket=ticket)
-  return endpoint_handlers_api.StorageRequest(
+    return handlers_endpoints.FinalizeRequest(upload_ticket=ticket)
+  return handlers_endpoints.StorageRequest(
       upload_ticket=ticket, content=content)
 
 
-validate = endpoint_handlers_api.TokenSigner.validate
+validate = handlers_endpoints.TokenSigner.validate
 
 
-def pad_string(string, size=endpoint_handlers_api.MIN_SIZE_FOR_DIRECT_GS):
+def pad_string(string, size=handlers_endpoints.MIN_SIZE_FOR_DIRECT_GS):
   return string + '0' * (size - len(string))
 
 
@@ -120,7 +114,7 @@
 class IsolateServiceTest(test_case.EndpointsTestCase):
   """Test the IsolateService's API methods."""
 
-  api_service_cls = endpoint_handlers_api.IsolateService
+  api_service_cls = handlers_endpoints.IsolateService
   gs_prefix = 'localhost:80/content-gs/store/'
   store_prefix = 'https://sample-app.storage.googleapis.com/'
 
@@ -135,6 +129,8 @@
     self.mock(utils, 'get_task_queue_host', lambda: version)
     self.testbed.setup_env(current_version_id='testbed.version')
     self.source_ip = '127.0.0.1'
+    # It is needed solely for self.execute_tasks(), which processes tasks queues
+    # on the backend application.
     self.app = webtest.TestApp(
         webapp2.WSGIApplication(handlers_backend.get_routes(), debug=True),
         extra_environ={'REMOTE_ADDR': self.source_ip})
@@ -169,8 +165,11 @@
         'preupload', self.message_to_dict(good_digests), 200)
     message = response.json.get(u'items', [{}])[0]
     self.assertEqual('', message.get(u'gs_upload_url', ''))
+    expected = validate(
+        message.get(u'upload_ticket', ''),
+        handlers_endpoints.UPLOAD_MESSAGES[0])
     self.assertEqual(
-        validate(message.get(u'upload_ticket', ''), UPLOAD_MESSAGES[0]),
+        expected,
         generate_embedded(good_digests.namespace, good_digests.items[0]))
 
   def test_finalize_url_ok(self):
@@ -181,26 +180,29 @@
     message = response.json.get(u'items', [{}])[0]
     self.assertTrue(message.get(u'gs_upload_url', '').startswith(
         self.store_prefix))
+    expected = validate(
+        message.get(u'upload_ticket', ''),
+        handlers_endpoints.UPLOAD_MESSAGES[1])
     self.assertEqual(
-        validate(message.get(u'upload_ticket', ''), UPLOAD_MESSAGES[1]),
+        expected,
         generate_embedded(digests.namespace, digests.items[0]))
 
   def test_pre_upload_invalid_hash(self):
     """Assert that status 400 is returned when the digest is invalid."""
-    bad_collection = DigestCollection(
-        namespace=endpoint_handlers_api.Namespace())
+    bad_collection = handlers_endpoints.DigestCollection(
+        namespace=handlers_endpoints.Namespace())
     bad_digest = hash_content('some stuff', bad_collection.namespace.namespace)
     bad_digest = 'g' + bad_digest[1:]  # that's not hexadecimal!
     bad_collection.items.append(
-        endpoint_handlers_api.Digest(digest=bad_digest, size=10))
+        handlers_endpoints.Digest(digest=bad_digest, size=10))
     with self.call_should_fail('400'):
       self.call_api(
           'preupload', self.message_to_dict(bad_collection), 200)
 
   def test_pre_upload_invalid_namespace(self):
     """Assert that status 400 is returned when the namespace is invalid."""
-    bad_collection = DigestCollection(
-        namespace=endpoint_handlers_api.Namespace(namespace='~tildewhatevs'))
+    bad_collection = handlers_endpoints.DigestCollection(
+        namespace=handlers_endpoints.Namespace(namespace='~tildewhatevs'))
     bad_collection.items.append(
         generate_digest('pangolin', bad_collection.namespace.namespace))
     with self.call_should_fail('400'):
@@ -231,7 +233,8 @@
 
   def test_check_existing_enqueues_tasks(self):
     """Assert that existent entities are enqueued."""
-    collection = DigestCollection(namespace=endpoint_handlers_api.Namespace())
+    collection = handlers_endpoints.DigestCollection(
+        namespace=handlers_endpoints.Namespace())
     collection.items.append(
         generate_digest('some content', collection.namespace))
     key = model.entry_key(
@@ -249,7 +252,8 @@
   def test_store_inline_ok(self):
     """Assert that inline content storage completes successfully."""
     request = self.store_request('sibilance')
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     key = model.entry_key(embedded['n'], embedded['d'])
 
     # assert that store_inline puts the correct entity into the datastore
@@ -261,12 +265,13 @@
     # assert that expected (digest, size) pair is generated by stored content
     self.assertEqual(
         (embedded['d'].encode('utf-8'), int(embedded['s'])),
-        endpoint_handlers_api.hash_content(stored.content, embedded['n']))
+        handlers_endpoints.hash_content(stored.content, embedded['n']))
 
   def test_store_inline_empty_content(self):
     """Assert that inline content storage works when content is empty."""
     request = self.store_request('')
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     key = model.entry_key(embedded['n'], embedded['d'])
 
     # assert that store_inline puts the correct entity into the datastore
@@ -278,7 +283,7 @@
     # assert that expected (digest, size) pair is generated by stored content
     self.assertEqual(
         (embedded['d'].encode('utf-8'), int(embedded['s'])),
-        endpoint_handlers_api.hash_content(stored.content, embedded['n']))
+        handlers_endpoints.hash_content(stored.content, embedded['n']))
 
   def test_store_inline_bad_mac(self):
     """Assert that inline content storage fails when token is altered."""
@@ -334,7 +339,8 @@
     """Assert that finalize_gs_upload creates a content entry."""
     content = pad_string('empathy')
     request = self.store_request(content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[1])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[1])
     key = model.entry_key(embedded['n'], embedded['d'])
 
     # finalize_gs_upload should put a new ContentEntry into the database
@@ -403,11 +409,12 @@
     """Assert that content retrieval goes off swimmingly in the normal case."""
     content = 'Grecian Urn'
     request = self.store_request(content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     self.call_api(
         'store_inline', self.message_to_dict(request), 200)
-    retrieve_request = endpoint_handlers_api.RetrieveRequest(
-        digest=embedded['d'], namespace=endpoint_handlers_api.Namespace())
+    retrieve_request = handlers_endpoints.RetrieveRequest(
+        digest=embedded['d'], namespace=handlers_endpoints.Namespace())
     response = self.call_api(
         'retrieve', self.message_to_dict(retrieve_request), 200)
     retrieved = response.json
@@ -417,11 +424,12 @@
     """Assert that content retrieval works for non-memcached DB entities."""
     content = 'Isabella, or the Pot of Basil'
     request = self.store_request(content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     self.call_api(
         'store_inline', self.message_to_dict(request), 200)
-    retrieve_request = endpoint_handlers_api.RetrieveRequest(
-        digest=embedded['d'], namespace=endpoint_handlers_api.Namespace())
+    retrieve_request = handlers_endpoints.RetrieveRequest(
+        digest=embedded['d'], namespace=handlers_endpoints.Namespace())
     memcache.flush_all()
     response = self.call_api(
         'retrieve', self.message_to_dict(retrieve_request), 200)
@@ -440,14 +448,15 @@
 
     # finalize GS upload
     request = preupload_status_to_request(message, content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[1])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[1])
     self.mock(gcs, 'get_file_info', get_file_info_factory(content))
     self.call_api(
         'finalize_gs_upload', self.message_to_dict(request), 200)
 
     # retrieve the upload URL
-    retrieve_request = endpoint_handlers_api.RetrieveRequest(
-        digest=embedded['d'], namespace=endpoint_handlers_api.Namespace())
+    retrieve_request = handlers_endpoints.RetrieveRequest(
+        digest=embedded['d'], namespace=handlers_endpoints.Namespace())
     retrieved_response = self.call_api(
         'retrieve', self.message_to_dict(retrieve_request), 200)
     retrieved = retrieved_response.json
@@ -463,12 +472,13 @@
     content = 'Song of the Andoumboulou'
     offset = 5
     request = self.store_request(content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     self.call_api(
         'store_inline', self.message_to_dict(request), 200)
-    retrieve_request = endpoint_handlers_api.RetrieveRequest(
+    retrieve_request = handlers_endpoints.RetrieveRequest(
         digest=embedded['d'],
-        namespace=endpoint_handlers_api.Namespace(),
+        namespace=handlers_endpoints.Namespace(),
         offset=offset)  # TODO(cmassaro): determine where offsets come from
     response = self.call_api(
         'retrieve', self.message_to_dict(retrieve_request), 200)
@@ -480,12 +490,13 @@
     """Assert that retrieval fails with status 416 when offset is invalid."""
     content = 'Of Man\'s first Disobedience, and the Fruit'
     request = self.store_request(content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
     self.call_api(
         'store_inline', self.message_to_dict(request), 200)
-    requests = [endpoint_handlers_api.RetrieveRequest(
+    requests = [handlers_endpoints.RetrieveRequest(
         digest=embedded['d'],
-        namespace=endpoint_handlers_api.Namespace(),
+        namespace=handlers_endpoints.Namespace(),
         offset=offset) for offset in [-1, len(content) + 1]]
     for request in requests:
       with self.call_should_fail('400'):
@@ -505,11 +516,12 @@
 
     # get the digest
     request = preupload_status_to_request(message, content)
-    embedded = validate(request.upload_ticket, UPLOAD_MESSAGES[0])
+    embedded = validate(
+        request.upload_ticket, handlers_endpoints.UPLOAD_MESSAGES[0])
 
     # don't upload data; try to retrieve
-    retrieve_request = endpoint_handlers_api.RetrieveRequest(
-        digest=embedded['d'], namespace=endpoint_handlers_api.Namespace())
+    retrieve_request = handlers_endpoints.RetrieveRequest(
+        digest=embedded['d'], namespace=handlers_endpoints.Namespace())
     with self.call_should_fail('404'):
       self.call_api(
           'retrieve', self.message_to_dict(retrieve_request), 200)
diff --git a/appengine/isolate/handlers_frontend.py b/appengine/isolate/handlers_frontend.py
index 4def64b..3ea3444 100644
--- a/appengine/isolate/handlers_frontend.py
+++ b/appengine/isolate/handlers_frontend.py
@@ -259,7 +259,7 @@
   ]
 
 
-def create_application(debug=False):
+def create_application(debug):
   """Creates the url router.
 
   The basic layouts is as follow:
diff --git a/appengine/isolate/main_backend.py b/appengine/isolate/main_backend.py
index 03528f4..c1e966d 100644
--- a/appengine/isolate/main_backend.py
+++ b/appengine/isolate/main_backend.py
@@ -2,16 +2,18 @@
 # Use of this source code is governed by the Apache v2.0 license that can be
 # found in the LICENSE file.
 
-"""This module is the entry point to load the AppEngine instance."""
+"""This modules is imported by AppEngine and defines the 'app' object.
+
+It is a separate file so that application bootstrapping code like ereporter2,
+that shouldn't be done in unit tests, can be done safely. This file must be
+tested via a smoke test.
+"""
 
 import os
 import sys
 
-from google.appengine.ext.appstats import recording
-
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-sys.path.insert(0, os.path.join(BASE_DIR, 'third_party'))
-sys.path.insert(0, os.path.join(BASE_DIR, 'components', 'third_party'))
+APP_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, os.path.join(APP_DIR, 'components', 'third_party'))
 
 from components import ereporter2
 from components import utils
@@ -20,18 +22,8 @@
 
 
 def create_application():
-  """Bootstraps the app and creates the url router."""
   ereporter2.register_formatter()
-  a = handlers_backend.create_application()
-  # In theory we'd want to take the output of app_identity.get_application_id().
-  # Sadly, this function does an RPC call and may contribute to cause time out
-  # on the initial load.
-  # Doing it here instead of appengine_config.py reduce the scope of appstats
-  # recording. To clarify, this means mapreduces started with mapreduce_jobs.py
-  # won't be instrumented, which is actually what we want in practice.
-  if utils.is_canary():
-    a = recording.appstats_wsgi_middleware(a)
-  return a
+  return handlers_backend.create_application(False)
 
 
 app = create_application()
diff --git a/appengine/isolate/main_frontend.py b/appengine/isolate/main_frontend.py
index f67cad6..72daf57 100644
--- a/appengine/isolate/main_frontend.py
+++ b/appengine/isolate/main_frontend.py
@@ -2,36 +2,36 @@
 # Use of this source code is governed by the Apache v2.0 license that can be
 # found in the LICENSE file.
 
-"""This module is the entry point to load the AppEngine instance."""
+"""This modules is imported by AppEngine and defines the 'app' object.
+
+It is a separate file so that application bootstrapping code like ereporter2,
+that shouldn't be done in unit tests, can be done safely. This file must be
+tested via a smoke test.
+"""
 
 import os
 import sys
 
-from google.appengine.ext.appstats import recording
+import endpoints
 
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-sys.path.insert(0, os.path.join(BASE_DIR, 'third_party'))
-sys.path.insert(0, os.path.join(BASE_DIR, 'components', 'third_party'))
+APP_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, os.path.join(APP_DIR, 'components', 'third_party'))
 
 from components import ereporter2
 from components import utils
 
+import handlers_endpoints
 import handlers_frontend
 
 
 def create_application():
-  """Bootstraps the app and creates the url router."""
   ereporter2.register_formatter()
-  a = handlers_frontend.create_application()
-  # In theory we'd want to take the output of app_identity.get_application_id().
-  # Sadly, this function does an RPC call and may contribute to cause time out
-  # on the initial load.
-  # Doing it here instead of appengine_config.py reduce the scope of appstats
-  # recording. To clarify, this means mapreduces started with mapreduce_jobs.py
-  # won't be instrumented, which is actually what we want in practice.
-  if utils.is_canary():
-    a = recording.appstats_wsgi_middleware(a)
-  return a
+
+  # App that serves HTML pages and old API.
+  frontend = handlers_frontend.create_application(False)
+  # App that serves new endpoints API.
+  api = endpoints.api_server([handlers_endpoints.isolate_api])
+  return frontend, api
 
 
-app = create_application()
+frontend_app, endpoints_app = create_application()
diff --git a/appengine/isolate/mapreduce_jobs.py b/appengine/isolate/mapreduce_jobs.py
index 366695a..c8c9b17 100644
--- a/appengine/isolate/mapreduce_jobs.py
+++ b/appengine/isolate/mapreduce_jobs.py
@@ -2,12 +2,16 @@
 # Use of this source code is governed by the Apache v2.0 license that can be
 # found in the LICENSE file.
 
-"""Map reduce jobs to update the DB schemas or any other maintenance task."""
+"""Defines the mapreduces, which are used to do one-off mass updates on entities
+and other manually triggered maintenance tasks.
+
+Automatically triggered maintenance tasks should use a task queue on the backend
+instead.
+"""
 
 import logging
 
 from mapreduce import control
-from mapreduce import main
 
 import config
 import gcs
@@ -80,7 +84,3 @@
     # think that entity exists.
     entry.key.delete(use_memcache=True)
     logging.error('MR: deleted bad entry\n%s', entry.key.id())
-
-
-# Export mapreduce WSGI application as 'app' for *.yaml routes.
-app = main.APP
diff --git a/appengine/isolate/module-backend.yaml b/appengine/isolate/module-backend.yaml
index c8a1389..5a51776 100644
--- a/appengine/isolate/module-backend.yaml
+++ b/appengine/isolate/module-backend.yaml
@@ -9,7 +9,7 @@
 
 handlers:
 - url: /internal/mapreduce(/.*)?
-  script: mapreduce_jobs.app
+  script: mapreduce.main.APP
   secure: always
   login: admin
 
@@ -19,7 +19,6 @@
   login: admin
 
 builtins:
-- appstats: on
 - remote_api: on
 
 includes: