thirdparty/google_appengine/google/appengine/tools/appcfg.py
changeset 1278 a7766286a7be
parent 828 f5fd65cc3bf3
child 2172 ac7bd3b467ff
equal deleted inserted replaced
1277:5c931bd3dc1e 1278:a7766286a7be
    69 
    69 
    70 verbosity = 1
    70 verbosity = 1
    71 
    71 
    72 
    72 
    73 appinfo.AppInfoExternal.ATTRIBUTES[appinfo.RUNTIME] = "python"
    73 appinfo.AppInfoExternal.ATTRIBUTES[appinfo.RUNTIME] = "python"
       
    74 _api_versions = os.environ.get('GOOGLE_TEST_API_VERSIONS', '1')
       
    75 _options = validation.Options(*_api_versions.split(','))
       
    76 appinfo.AppInfoExternal.ATTRIBUTES[appinfo.API_VERSION] = _options
       
    77 del _api_versions, _options
    74 
    78 
    75 
    79 
    76 def StatusUpdate(msg):
    80 def StatusUpdate(msg):
    77   """Print a status message to stderr.
    81   """Print a status message to stderr.
    78 
    82 
   186     version = yaml.safe_load(version_fh)
   190     version = yaml.safe_load(version_fh)
   187   finally:
   191   finally:
   188     version_fh.close()
   192     version_fh.close()
   189 
   193 
   190   return version
   194   return version
       
   195 
       
   196 def RetryWithBackoff(initial_delay, backoff_factor, max_tries, callable):
       
   197     """Calls a function multiple times, backing off more and more each time.
       
   198 
       
   199     Args:
       
   200       initial_delay: Initial delay after first try, in seconds.
       
   201       backoff_factor: Delay will be multiplied by this factor after each try.
       
   202       max_tries: Maximum number of tries.
       
   203       callable: The method to call, will pass no arguments.
       
   204 
       
   205     Returns:
       
   206       True if the function succeded in one of its tries.
       
   207 
       
   208     Raises:
       
   209       Whatever the function raises--an exception will immediately stop retries.
       
   210     """
       
   211     delay = initial_delay
       
   212     while not callable() and max_tries > 0:
       
   213       StatusUpdate("Will check again in %s seconds." % delay)
       
   214       time.sleep(delay)
       
   215       delay *= backoff_factor
       
   216       max_tries -= 1
       
   217     return max_tries > 0
   191 
   218 
   192 
   219 
   193 class UpdateCheck(object):
   220 class UpdateCheck(object):
   194   """Determines if the local SDK is the latest version.
   221   """Determines if the local SDK is the latest version.
   195 
   222 
   787 
   814 
   788   Note that the server doesn't report its local time (the HTTP Date
   815   Note that the server doesn't report its local time (the HTTP Date
   789   header uses UTC), and the client's local time is irrelevant.
   816   header uses UTC), and the client's local time is irrelevant.
   790 
   817 
   791   Args:
   818   Args:
   792     A posix timestamp giving current UTC time.
   819     now: A posix timestamp giving current UTC time.
   793 
   820 
   794   Returns:
   821   Returns:
   795     A pseudo-posix timestamp giving current Pacific time.  Passing
   822     A pseudo-posix timestamp giving current Pacific time.  Passing
   796     this through time.gmtime() will produce a tuple in Pacific local
   823     this through time.gmtime() will produce a tuple in Pacific local
   797     time.
   824     time.
   911     version: The version string from 'config'.
   938     version: The version string from 'config'.
   912     files: A dictionary of files to upload to the server, mapping path to
   939     files: A dictionary of files to upload to the server, mapping path to
   913       hash of the file contents.
   940       hash of the file contents.
   914     in_transaction: True iff a transaction with the server has started.
   941     in_transaction: True iff a transaction with the server has started.
   915       An AppVersionUpload can do only one transaction at a time.
   942       An AppVersionUpload can do only one transaction at a time.
       
   943     deployed: True iff the Deploy method has been called.
   916   """
   944   """
   917 
   945 
   918   def __init__(self, server, config):
   946   def __init__(self, server, config):
   919     """Creates a new AppVersionUpload.
   947     """Creates a new AppVersionUpload.
   920 
   948 
   928     self.config = config
   956     self.config = config
   929     self.app_id = self.config.application
   957     self.app_id = self.config.application
   930     self.version = self.config.version
   958     self.version = self.config.version
   931     self.files = {}
   959     self.files = {}
   932     self.in_transaction = False
   960     self.in_transaction = False
       
   961     self.deployed = False
   933 
   962 
   934   def _Hash(self, content):
   963   def _Hash(self, content):
   935     """Compute the hash of the content.
   964     """Compute the hash of the content.
   936 
   965 
   937     Args:
   966     Args:
  1057     """Commits the transaction, making the new app version available.
  1086     """Commits the transaction, making the new app version available.
  1058 
  1087 
  1059     All the files returned by Begin() must have been uploaded with UploadFile()
  1088     All the files returned by Begin() must have been uploaded with UploadFile()
  1060     before Commit() can be called.
  1089     before Commit() can be called.
  1061 
  1090 
       
  1091     This tries the new 'deploy' method; if that fails it uses the old 'commit'.
       
  1092 
  1062     Raises:
  1093     Raises:
  1063       Exception: Some required files were not uploaded.
  1094       Exception: Some required files were not uploaded.
  1064     """
  1095     """
  1065     assert self.in_transaction, "Begin() must be called before Commit()."
  1096     assert self.in_transaction, "Begin() must be called before Commit()."
  1066     if self.files:
  1097     if self.files:
  1067       raise Exception("Not all required files have been uploaded.")
  1098       raise Exception("Not all required files have been uploaded.")
  1068 
  1099 
  1069     StatusUpdate("Closing update.")
  1100     try:
  1070     self.server.Send("/api/appversion/commit", app_id=self.app_id,
  1101       self.Deploy()
       
  1102       if not RetryWithBackoff(1, 2, 8, self.IsReady):
       
  1103         logging.warning("Version still not ready to serve, aborting.")
       
  1104         raise Exception("Version not ready.")
       
  1105       self.StartServing()
       
  1106     except urllib2.HTTPError, e:
       
  1107       if e.code != 404:
       
  1108         raise
       
  1109       StatusUpdate("Closing update.")
       
  1110       self.server.Send("/api/appversion/commit", app_id=self.app_id,
       
  1111                        version=self.version)
       
  1112       self.in_transaction = False
       
  1113 
       
  1114   def Deploy(self):
       
  1115     """Deploys the new app version but does not make it default.
       
  1116 
       
  1117     All the files returned by Begin() must have been uploaded with UploadFile()
       
  1118     before Deploy() can be called.
       
  1119 
       
  1120     Raises:
       
  1121       Exception: Some required files were not uploaded.
       
  1122     """
       
  1123     assert self.in_transaction, "Begin() must be called before Deploy()."
       
  1124     if self.files:
       
  1125       raise Exception("Not all required files have been uploaded.")
       
  1126 
       
  1127     StatusUpdate("Deploying new version.")
       
  1128     self.server.Send("/api/appversion/deploy", app_id=self.app_id,
  1071                      version=self.version)
  1129                      version=self.version)
  1072     self.in_transaction = False
  1130     self.deployed = True
       
  1131 
       
  1132   def IsReady(self):
       
  1133     """Check if the new app version is ready to serve traffic.
       
  1134 
       
  1135     Raises:
       
  1136       Exception: Deploy has not yet been called.
       
  1137 
       
  1138     Returns:
       
  1139       True if the server returned the app is ready to serve.
       
  1140     """
       
  1141     assert self.deployed, "Deploy() must be called before IsReady()."
       
  1142 
       
  1143     StatusUpdate("Checking if new version is ready to serve.")
       
  1144     result = self.server.Send("/api/appversion/isready", app_id=self.app_id,
       
  1145                               version=self.version)
       
  1146     return result == "1"
       
  1147 
       
  1148   def StartServing(self):
       
  1149     """Start serving with the newly created version.
       
  1150 
       
  1151     Raises:
       
  1152       Exception: Deploy has not yet been called.
       
  1153     """
       
  1154     assert self.deployed, "Deploy() must be called before IsReady()."
       
  1155 
       
  1156     StatusUpdate("Closing update: new version is ready to start serving.")
       
  1157     self.server.Send("/api/appversion/startserving",
       
  1158                      app_id=self.app_id, version=self.version)
  1073 
  1159 
  1074   def Rollback(self):
  1160   def Rollback(self):
  1075     """Rolls back the transaction if one is in progress."""
  1161     """Rolls back the transaction if one is in progress."""
  1076     if not self.in_transaction:
  1162     if not self.in_transaction:
  1077       return
  1163       return
  1138           num_files += 1
  1224           num_files += 1
  1139           if num_files % 500 == 0:
  1225           if num_files % 500 == 0:
  1140             StatusUpdate("Uploaded %d files." % num_files)
  1226             StatusUpdate("Uploaded %d files." % num_files)
  1141 
  1227 
  1142       self.Commit()
  1228       self.Commit()
       
  1229 
  1143     except KeyboardInterrupt:
  1230     except KeyboardInterrupt:
  1144       logging.info("User interrupted. Aborting.")
  1231       logging.info("User interrupted. Aborting.")
  1145       self.Rollback()
  1232       self.Rollback()
  1146       raise
  1233       raise
  1147     except:
  1234     except:
  1148       logging.error("An unexpected error occurred. Aborting.")
  1235       logging.exception("An unexpected error occurred. Aborting.")
  1149       self.Rollback()
  1236       self.Rollback()
  1150       raise
  1237       raise
  1151 
  1238 
  1152     logging.info("Done!")
  1239     logging.info("Done!")
  1153 
  1240 
  1269 
  1356 
  1270   def __init__(self, argv, parser_class=optparse.OptionParser,
  1357   def __init__(self, argv, parser_class=optparse.OptionParser,
  1271                rpc_server_class=appengine_rpc.HttpRpcServer,
  1358                rpc_server_class=appengine_rpc.HttpRpcServer,
  1272                raw_input_fn=raw_input,
  1359                raw_input_fn=raw_input,
  1273                password_input_fn=getpass.getpass,
  1360                password_input_fn=getpass.getpass,
  1274                error_fh=sys.stderr):
  1361                error_fh=sys.stderr,
       
  1362                update_check_class=UpdateCheck):
  1275     """Initializer.  Parses the cmdline and selects the Action to use.
  1363     """Initializer.  Parses the cmdline and selects the Action to use.
  1276 
  1364 
  1277     Initializes all of the attributes described in the class docstring.
  1365     Initializes all of the attributes described in the class docstring.
  1278     Prints help or error messages if there is an error parsing the cmdline.
  1366     Prints help or error messages if there is an error parsing the cmdline.
  1279 
  1367 
  1282       parser_class: Options parser to use for this application.
  1370       parser_class: Options parser to use for this application.
  1283       rpc_server_class: RPC server class to use for this application.
  1371       rpc_server_class: RPC server class to use for this application.
  1284       raw_input_fn: Function used for getting user email.
  1372       raw_input_fn: Function used for getting user email.
  1285       password_input_fn: Function used for getting user password.
  1373       password_input_fn: Function used for getting user password.
  1286       error_fh: Unexpected HTTPErrors are printed to this file handle.
  1374       error_fh: Unexpected HTTPErrors are printed to this file handle.
       
  1375       update_check_class: UpdateCheck class (can be replaced for testing).
  1287     """
  1376     """
  1288     self.parser_class = parser_class
  1377     self.parser_class = parser_class
  1289     self.argv = argv
  1378     self.argv = argv
  1290     self.rpc_server_class = rpc_server_class
  1379     self.rpc_server_class = rpc_server_class
  1291     self.raw_input_fn = raw_input_fn
  1380     self.raw_input_fn = raw_input_fn
  1292     self.password_input_fn = password_input_fn
  1381     self.password_input_fn = password_input_fn
  1293     self.error_fh = error_fh
  1382     self.error_fh = error_fh
       
  1383     self.update_check_class = update_check_class
  1294 
  1384 
  1295     self.parser = self._GetOptionParser()
  1385     self.parser = self._GetOptionParser()
  1296     for action in self.actions.itervalues():
  1386     for action in self.actions.itervalues():
  1297       action.options(self, self.parser)
  1387       action.options(self, self.parser)
  1298 
  1388 
  1569 
  1659 
  1570     basepath = self.args[0]
  1660     basepath = self.args[0]
  1571     appyaml = self._ParseAppYaml(basepath)
  1661     appyaml = self._ParseAppYaml(basepath)
  1572     rpc_server = self._GetRpcServer()
  1662     rpc_server = self._GetRpcServer()
  1573 
  1663 
  1574     updatecheck = UpdateCheck(rpc_server, appyaml)
  1664     updatecheck = self.update_check_class(rpc_server, appyaml)
  1575     updatecheck.CheckForUpdates()
  1665     updatecheck.CheckForUpdates()
  1576 
  1666 
  1577     appversion = AppVersionUpload(rpc_server, appyaml)
  1667     appversion = AppVersionUpload(rpc_server, appyaml)
  1578     appversion.DoUpload(FileIterator(basepath), self.options.max_size,
  1668     appversion.DoUpload(FileIterator(basepath), self.options.max_size,
  1579                         lambda path: open(os.path.join(basepath, path), "rb"))
  1669                         lambda path: open(os.path.join(basepath, path), "rb"))
  1601 
  1691 
  1602     Args:
  1692     Args:
  1603       parser: An instance of OptionsParser.
  1693       parser: An instance of OptionsParser.
  1604     """
  1694     """
  1605     parser.add_option("-S", "--max_size", type="int", dest="max_size",
  1695     parser.add_option("-S", "--max_size", type="int", dest="max_size",
  1606                       default=1048576, metavar="SIZE",
  1696                       default=10485760, metavar="SIZE",
  1607                       help="Maximum size of a file to upload.")
  1697                       help="Maximum size of a file to upload.")
  1608 
  1698 
  1609   def VacuumIndexes(self):
  1699   def VacuumIndexes(self):
  1610     """Deletes unused indexes."""
  1700     """Deletes unused indexes."""
  1611     if len(self.args) != 1:
  1701     if len(self.args) != 1: