def _SetManagedBy(self, service, version, vm_name, url, wait): """Switches a service version between management modes. Args: service: str, The service to update. version: str, The version of the service to update. vm_name: str, The vm name of the instance to update. url: str, The URL of the API to call to make the update. wait: bool, True to wait until it takes effect. Raises: Error: if changing the instance debug state failed. """ rpcserver = self._GetRpcServer() kwargs = { 'app_id': self.project, 'version_match': version, 'module': service } if vm_name: kwargs['instance'] = vm_name rpcserver.Send(url, **kwargs) if wait: def GetState(): yaml_data = rpcserver.Send('/api/vms/debugstate', app_id=self.project, version_match=version, module=service) state = yaml.safe_load(yaml_data) done = state['state'] != 'PENDING' return (done, (state['state'], state['message'])) def PrintRetryMessage((unused_state, msg), delay): log.status.Print('{0}. Will try again in {1} seconds.'.format( msg, delay)) _, (state, message) = util.RetryWithBackoff(GetState, PrintRetryMessage, initial_delay=1, backoff_factor=2, max_delay=5, max_tries=20) if state == 'ERROR': raise Error(message)
def _SetManagedBy(self, module, version, instance, url, wait): """Switches a module version between management modes. Args: module: str, The module to update. version: str, The version of the module to update. instance: str, The instance id of a single instance to update. url: str, The URL of the API to call to make the update. wait: bool, True to wait until it takes effect. Returns: None, if not waiting. If waiting, returns (bool, message) for the last attempt at checking state. """ rpcserver = self._GetRpcServer() kwargs = { 'app_id': self.project, 'version_match': version, 'module': module } if instance: kwargs['instance'] = instance rpcserver.Send(url, **kwargs) if wait: def GetState(): yaml_data = rpcserver.Send('/api/vms/debugstate', app_id=self.project, version_match=version, module=module) state = yaml.safe_load(yaml_data) done = state['state'] != 'PENDING' return (done, state['message']) def PrintRetryMessage(msg, delay): log.status.Print('{0}. Will try again in {1} seconds.'.format( msg, delay)) return util.RetryWithBackoff(GetState, PrintRetryMessage, initial_delay=1, backoff_factor=2, max_delay=5, max_tries=20)
def RetryWithBackoff(self, *args, **kwargs): return util.RetryWithBackoff(*args, **kwargs)
def Download(self, full_version, file_lines, output_dir, progress_callback=None): """Downloads all the files in the module. Args: full_version: str, The full version id of the module that is being downloaded. This comes from the list files API call. file_lines: [str], The list of files to download. The files are in the format ID|SIZE|PATH. output_dir: str, The path to download the files to. progress_callback: console_io.ProgressTracker.SetProgress, A function reference to use for updating progress if set. Raises: InvalidFileListError: If the file list from the server is invalid. FileDownloadFailedError: If download an individual file failed. FileWriteFailedError: If the file contents could not be written to disk. """ log.info('Fetching files...') # Use a float because of the progress tracker. num_files = float(len(file_lines)) current_file_num = 0 for line in file_lines: parts = line.split('|', 2) if len(parts) != 3: raise InvalidFileListError( 'Invalid response from server: expecting [<id>|<size>|<path>], ' 'found: [{0}]'.format(line)) file_id, size_str, path = parts current_file_num += 1 log.debug('Found file: [%d / %d] %s', current_file_num, num_files, path) try: size = int(size_str) except ValueError: raise InvalidFileListError( 'Invalid file list entry from server: invalid size: [{0}]'. format(size_str)) def PrintRetryMessage(msg, delay): log.status.Print('{0}. Will try again in {1} seconds.'.format( msg, delay)) # pylint: disable=cell-var-from-loop, This closure doesn't get used # outside the context of the loop iteration. success, contents = util.RetryWithBackoff( lambda: self._GetFile(full_version, file_id), PrintRetryMessage) if not success: raise FileDownloadFailedError( 'Unable to download file: [{0}]'.format(path)) if len(contents) != size: raise FileDownloadFailedError( 'File [{file}]: server listed as [{expected}] bytes but served ' '[{actual}] bytes.'.format(file=path, expected=size, actual=len(contents))) full_path = os.path.join(output_dir, path) if os.path.exists(full_path): raise FileWriteFailedError( 'Unable to create file [{0}]: path conflicts with an existing file ' 'or directory'.format(path)) full_dir = os.path.dirname(full_path) try: files.MakeDir(full_dir) except OSError as e: raise FileWriteFailedError( 'Failed to create directory [{0}]: {1}'.format( full_dir, e)) try: with open(full_path, 'wb') as f: f.write(contents) except IOError as e: raise FileWriteFailedError( 'Failed to write to file [{0}]: {1}'.format(full_path, e)) if progress_callback: progress_callback(current_file_num / num_files)
def Commit(self): """Commits the transaction, making the new app version available. All the files returned by Begin() must have been uploaded with UploadFile() before Commit() can be called. This tries the new 'deploy' method; if that fails it uses the old 'commit'. Returns: An appinfo.AppInfoSummary if one was returned from the Deploy, None otherwise. Raises: Error: Some required files were not uploaded. CannotStartServingError: Another operation is in progress on this version. """ assert self.in_transaction, 'Begin() must be called before Commit().' if self.files: raise Error('Not all required files have been uploaded.') def PrintRetryMessage(_, delay): log.debug('Will check again in %s seconds.' % delay) app_summary = self.Deploy() # These backoff numbers seem reasonable and allow up to 15 minutes. success, unused_contents = util.RetryWithBackoff( lambda: (self.IsReady(), None), PrintRetryMessage, 1, 2, 60, 20) if not success: # TODO(mblain): Nicer exception, and handle printing it below. log.warn('Version still not ready to serve, aborting.') raise Error('Version not ready.') result = self.StartServing() if not result: # This is an old version of the admin console (i.e. 1.5.0 or # earlier). The update is now complete. self.in_transaction = False else: if result == '0': raise CannotStartServingError( 'Another operation on this version is in progress.') success, response = util.RetryNoBackoff(self.IsServing, PrintRetryMessage) if not success: # TODO(mblain): Nicer exception, and handle printing it below. log.warn('Version still not serving, aborting.') raise Error('Version not ready.') # If the server indicates that the Google Cloud Endpoints configuration # is going to be updated, wait until that configuration is updated. check_config_updated = response.get('check_endpoints_config') if check_config_updated: unused_done, (last_state, user_error) = util.RetryWithBackoff( self.IsEndpointsConfigUpdated, PrintRetryMessage, 1, 2, 60, 20) if last_state != EndpointsState.SERVING: self.HandleEndpointsError(user_error) self.in_transaction = False return app_summary