def __init__(self, inputQueue, communicationQueue, terminationEvent, logs_queue, cfg): """ @param inputQueue: multiprocess queue used to send pathname_operation to the child @param communcationQueue: multiprocess queue used to send back results @param terminationEvent: multiprocess event used to stop the upload/download @param logs_queue: multiprocess queue used to send back log messages @param cfg: instance of filerockclient.config.ConfigManager """ # Note: executed before the process has started. Every attribute set here must be picklable. multiprocessing.Process.__init__(self) self.logger = SubprocessLogger(logs_queue) self.inputQueue = inputQueue self.communicationQueue = communicationQueue self.terminationEvent = terminationEvent self.cfg = cfg
class WorkerChild(multiprocessing.Process): """ Handle the upload and download of files Communicates with the core within multiprocess queues """ def __init__(self, inputQueue, communicationQueue, terminationEvent, logs_queue, cfg): """ @param inputQueue: multiprocess queue used to send pathname_operation to the child @param communcationQueue: multiprocess queue used to send back results @param terminationEvent: multiprocess event used to stop the upload/download @param logs_queue: multiprocess queue used to send back log messages @param cfg: instance of filerockclient.config.ConfigManager """ # Note: executed before the process has started. Every attribute set here must be picklable. multiprocessing.Process.__init__(self) self.logger = SubprocessLogger(logs_queue) self.inputQueue = inputQueue self.communicationQueue = communicationQueue self.terminationEvent = terminationEvent self.cfg = cfg def __close_temp_file_fd(self, file_operation): if file_operation.verb == 'DOWNLOAD': if os.path.exists(file_operation.temp_pathname): os.close(file_operation.temp_fd) self.logger.debug(u'Temp fd %s closed' % file_operation.temp_fd) def __check_download_dir(self, download_dir): if os.path.exists(download_dir) and os.path.isdir(download_dir): return True elif os.path.exists(download_dir) and not os.path.isdir(download_dir): os.unlink(download_dir) elif not os.path.exists(download_dir): os.makedirs(download_dir) def __get_download_dir(self): temp_dir = os.path.join(self.cfg.get('User','temp_dir'), DOWNLOAD_DIR) self.__check_download_dir(temp_dir) return temp_dir def __get_temp_file(self, file_operation): if file_operation.verb == 'DOWNLOAD': temp_dir = self.__get_download_dir() temp_fd, temp_pathname = mkstemp(dir=temp_dir) file_operation.temp_pathname = temp_pathname file_operation.temp_fd = temp_fd def __rm_temp_file(self, file_operation): if file_operation.verb == 'DOWNLOAD': if os.path.exists(file_operation.temp_pathname): try: os.remove(file_operation.temp_pathname) self.logger.debug(u'Temp file %s deleted' % file_operation.temp_pathname) except: pass def _more_init(self): """ Executed after the process has started. Set here non-picklable attributes """ self.warebox = warebox.Warebox(self.cfg) self.connector = StorageConnector(self.warebox, self.cfg) self.logger.debug(u"Hello, my PID is %s" % self.pid) def run(self): """ Handles file operation received through the inputQueue until a poison pill is received, sends back logs and results through communicationQueue """ try: self._more_init() termination = False while not termination: try: operation, file_operation = self.inputQueue.get() self.logger.debug('==> Operation type %s, content %s' % (operation, file_operation)) if operation == 'FileOperation': self.terminationEvent.clear() self.logger.debug(u'Started to handle %s' % (file_operation)) self.__get_temp_file(file_operation) result, interrupted = self._handle_operation(file_operation) self.__rm_temp_file(file_operation) if result: self.logger.debug(u'Operation completed: %s. Returning.' % (file_operation)) self.communicationQueue.put(('result','completed')) elif not result and interrupted: self.logger.debug(u'Failed performing operation %s. INTERRUPTED.' % (file_operation)) self.communicationQueue.put(('result', 'interrupted')) elif not result and not interrupted: self.logger.debug(u'Failed performing operation %s. Returning.' % (file_operation)) self.communicationQueue.put(('result', 'failed')) elif operation == 'PoisonPill': termination = True self.logger.debug(u"I'm going to die") self.communicationQueue.put(('ShuttingDown', None)) except KeyboardInterrupt: pass except Exception: self.communicationQueue.put(('DIED',None)) # self.percentuageQueue.put(None) def _handle_operation(self, file_operation): """ Handles a file_operation, uploading o downloading the associated file @param file_operation: instance of filerockclient.pathname_operation """ try: if file_operation.verb == 'UPLOAD': result = self._handle_upload_operation(file_operation) return result elif file_operation.verb == 'DOWNLOAD': result, interrupted = self._handle_download_operation(file_operation) if not interrupted: if result and file_operation.to_decrypt: CryptoHelpers.decrypt(file_operation, self.warebox, self.cfg, self.logger) self.__close_temp_file_fd(file_operation) self.warebox.move(file_operation.temp_pathname, file_operation.pathname, file_operation.conflicted) return (result, interrupted) else: self.logger.debug(u'Unsupported verb for operation, giving up: %s' % (file_operation)) return False, False except Exception as e: self.logger.debug(u'Exception caught: %s\n%s' % (e, traceback.format_exc())) return False, False except KeyboardInterrupt: self.logger.debug(u'Caught a KeyboardInterrupt, this means that the application is closing. I give up.') return False, True def _handle_upload_operation(self, file_operation): """ Handles upload operation Prepares useful data and delegates to the connector the upload @param file_operation: instance of filerockclient.pathname_operation """ if file_operation.to_encrypt: pathname = file_operation.encrypted_pathname open_function = open mode = 'rb' etag = file_operation.storage_etag size = file_operation.storage_size iv = file_operation.iv else: pathname = file_operation.pathname open_function = self.warebox.open mode = 'r' etag = file_operation.warebox_etag size = file_operation.warebox_size iv = None args = [ pathname, file_operation.upload_info['remote_pathname'], file_operation.upload_info['remote_ip_address'], file_operation.upload_info['bucket'], file_operation.upload_info['auth_token'], file_operation.upload_info['auth_date'], open_function, etag, size, iv ] do_upload = lambda event: self.connector.upload_file(*args, terminationEvent=event, percentageQueue=self.communicationQueue, logger=self.logger) return self._perform_network_transfer(do_upload, file_operation) def _handle_download_operation(self, file_operation): """ Handles download operation Prepares useful data and delegates to the connector the download @param file_operation: instance of filerockclient.pathname_operation """ if file_operation.to_decrypt: pathname = file_operation.encrypted_pathname open_function = open mode = 'wb' else: pathname = file_operation.temp_pathname open_function= open mode = 'wb' args = [ pathname, file_operation.pathname, file_operation.download_info['remote_ip_address'], file_operation.download_info['bucket'], file_operation.download_info['auth_token'], file_operation.download_info['auth_date'], open_function ] do_download = lambda event: self.connector.download_file(*args, terminationEvent=event, percentageQueue=self.communicationQueue, logger=self.logger) return self._perform_network_transfer(do_download, file_operation) def _perform_network_transfer(self, transfer_strategy, file_operation): """ Does a limited number of attempts to perform the given transfer. In case of failure a certain time interval is awaited and another attempt is performed. The waiting time is doubled each time until the maximum amount of attempts is reached. The transfer could be interrupted in any time by setting terminationEvent @param transfer_strategy: lambda function wrapping the transfer method @param file_operation: instance of filerockclient.pathname_operation """ # # if self.terminationQueue is not None: # event = threading.Event() # termination = TerminationThread(self.terminationQueue, event) # termination.start() max_attempts = 10 waiting_time = 1 attempts = 0 while not self.terminationEvent.is_set() and attempts <= max_attempts: self.logger.debug(u'Started network transfer for: %s "%s":' % (file_operation.verb, file_operation.pathname)) response = transfer_strategy(self.terminationEvent) if response['success']: self.logger.debug(u'Successfully ended network transfer for: %s "%s":' % (file_operation.verb, file_operation.pathname)) return (True, False) else: if 'termination' in response['details']: return (False, True) self.logger.warning(u'HTTP %s failed for operation: %s. Retrying in %s seconds...' % (file_operation.verb, file_operation, waiting_time)) self.logger.debug(u'Response details: %s' % (response['details'])) waiting_time = stoppable_exponential_backoff_waiting(waiting_time, self.terminationEvent) attempts += 1 if self.terminationEvent.is_set(): # termination required from outside return (False, True) self.logger.error(u'Ok, I have tried performing %s for %d times. I am done now. Put that stuff in a FedEx box and send it via mail.' % (file_operation, max_attempts)) return (False, False)