def on_version_hook_event(self, _, event): # Only handle version hook events if event.cargo[HOOK_COMMAND] != settings.hooks.version: return # hook should never run on force, force == skip it assert not self.job.force status = event.cargo[HOOK_STATUS] if status == HOOK_STATUS_COMPLETED: logger.debug("Version hook done") version = event.cargo[HOOK_MESSAGE] # Check if we do not already run on the version to be installed if self.job.version == version: logger.info(f"Version {self.job.version} is already running.") self.job_succeeded( JobSuccessStatus.VERSION_ALREADY_INSTALLED.value) return self.publish(pysm.Event(JOB_REVOKED)) else: logger.info( f"Running on version {version}. Install {self.job.version}" ) return self._job_verified() elif status in (HOOK_STATUS_FAILED, HOOK_STATUS_TIMED_OUT): error_message = event.cargo[HOOK_MESSAGE] logger.error(f"Version hook failed: {error_message}") self.job_failed(JobFailedStatus.VERSION_HOOK_FAILED.value, message=error_message) return self.publish(pysm.Event(JOB_REVOKED))
def on_enter(self, state, event): if self.job.status == JobStatus.QUEUED.value: version_hook = settings.hooks.version force = self.job.force if force or not version_hook: logger.info( f"Skip version check [force={force}, {version_hook if version_hook else 'no-hook'}]" # noqa ) return self._job_verified() logger.debug("Start version check") self.stop_version_hook = run_hook(version_hook, self.root_machine.inbox, args=[self.job.meta]) elif self.job.status == JobStatus.IN_PROGRESS.value: # If the restart is initiated the installation is done if self.job.internal_state == JobProgressStatus.REBOOT_START.value: logger.info("Installation done") self.publish( pysm.Event(JOB_INSTALLATION_DONE, **{JOB: self.job})) # Redo the whole update process else: logger.info("Redo job process") return self._job_verified() else: raise Exception(f"Unexpected job status: {self.job.status}")
def command_to_event(self, spieler_name, command): # please stick to the convention that event identifiers are the same # as the command strings if command == "einwerfen": event = pysm.Event("einwerfen", spieler_name=spieler_name) elif command == "wuerfeln": event = pysm.Event("wuerfeln", spieler_name=spieler_name) elif command == "stechen": event = pysm.Event("stechen", spieler_name=spieler_name) elif command == "weiter": event = pysm.Event("weiter", spieler_name=spieler_name) elif command == "beiseite": event = pysm.Event("beiseite", spieler_name=spieler_name) else: raise FalscheAktion self.dispatch(event)
def on_restart_hook_event(self, _, event): # Only handle restart hook events if event.cargo[HOOK_COMMAND] != settings.hooks.restart: return status = event.cargo[HOOK_STATUS] if status == HOOK_STATUS_COMPLETED: logger.info("Restart hook done") self.job_succeeded(JobSuccessStatus.COMPLETE_SOFT_RESTART.value) self.publish(pysm.Event(RESTART_INTERRUPTED)) elif status in (HOOK_STATUS_FAILED, HOOK_STATUS_TIMED_OUT): message = event.cargo[HOOK_MESSAGE] logger.error(f"Restart failed: {message}") self.job_failed(JobFailedStatus.RESTART_HOOK_FAILED.value, message=message) self.publish(pysm.Event(RESTART_INTERRUPTED))
def on_install_hook_event(self, _, event): # Only handle install hook events if event.cargo[HOOK_COMMAND] != settings.hooks.install: return status = event.cargo[HOOK_STATUS] if status == HOOK_STATUS_COMPLETED: logger.info("Installation hook done") self.publish(pysm.Event(INSTALLATION_DONE, **{JOB: self.job})) elif status == HOOK_STATUS_OUTPUT: self.job_progress( JobProgressStatus.INSTALLATION_PROGRESS.value, message=event.cargo[HOOK_MESSAGE], ) elif status in (HOOK_STATUS_FAILED, HOOK_STATUS_TIMED_OUT): error_message = event.cargo[HOOK_MESSAGE] logger.error(f"Installation failed: {error_message}") self.job_failed(JobFailedStatus.INSTALLATION_HOOK_FAILED.value, message=error_message) self.publish(pysm.Event(INSTALLATION_INTERRUPTED))
def weiter(self): try: self.rdm.weiter() except RundeVorbei: self.spielzeit_status = self.rdm.deckel_verteilen_restliche_spieler( ) if self.beendet(): self.verlierende = self.spielzeit_status.spieler[0] self.root_machine.dispatch(pysm.Event(events.FERTIG_HALBZEIT)) else: self.rdm = RundenDeckelManagement(self.spielzeit_status) # naechsten spieler zueruecksetzen self._spieler_zuruecksetzen(self.aktiver_spieler)
def on_enter(self, state, event): if settings.hooks.restart: logger.info("Initiate restart") self.job_progress(JobProgressStatus.REBOOT_START.value) self.stop_restart_hook = run_hook( settings.hooks.restart, self.root_machine.inbox, args=[self.job.meta, self.job.force], ) else: logger.info("No restart hook provided") self.job_succeeded(JobSuccessStatus.NO_RESTART_HOOK_PROVIDED.value) self.publish(pysm.Event(RESTART_INTERRUPTED))
def on_enter(self, state, event): # Start install hook if settings.hooks.install: logger.info("Start installation") self.job_progress(JobProgressStatus.INSTALLATION_START.value) self.stop_install_hook = run_hook( settings.hooks.install, self.root_machine.inbox, args=[self.job.meta, self.job.filepath], ) else: logger.info("No installation hook provided") # mark as succeeded because maybe there is no "install" step # necessary since we only want to distribute a file self.job_succeeded( JobSuccessStatus.NO_INSTALLATION_HOOK_PROVIDED.value) self.publish(pysm.Event(INSTALLATION_INTERRUPTED))
def on_handle_hooks(self, _, event): if event.cargo[HOOK_COMMAND] != settings.hooks.download: return status = event.cargo[HOOK_STATUS] if status == HOOK_STATUS_COMPLETED: logger.info("Hook successfully completed. Download now allowed.") self.start_download_thread() elif status in (HOOK_STATUS_FAILED, HOOK_STATUS_TIMED_OUT): error_message = event.cargo[HOOK_MESSAGE] logger.error(f"Version hook failed: {error_message}") self.job_failed( JobFailedStatus.DOWNLOAD_HOOK_FAILED.value, message=error_message ) self.publish(pysm.Event(DOWNLOAD_INTERRUPTED))
def on_job_cancelled(self, state, event): self._stop_hooks() self.publish(pysm.Event(JOB_REVOKED))
def _job_verified(self): self.publish(pysm.Event(JOB_VERIFIED, **{JOB: self.job}))
def on_job_cancelled(self, state, event): self._stop_hooks() self.publish(pysm.Event(INSTALLATION_INTERRUPTED))
def download(job, stop_download, publish, update_job_progress): """ See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information regarding the backoff behaviour (i.e. jitter). """ start_position_bytes = 0 if os.path.exists(job.filepath): start_position_bytes = os.path.getsize(job.filepath) logger.info(f"Partial download of {start_position_bytes} bytes found.") if stop_download.is_set(): logger.info(f"Download interrupted [stop event set].") request = urllib.request.Request(job.file_url) request.add_header("Range", f"bytes={start_position_bytes}-") logger.info(f"Downloading job to {job.filepath}.") try: with urllib.request.urlopen( request, timeout=REQUEST_TIMEOUT_SEC ) as source, open(job.filepath, "ab") as destination: done = False while not done and not stop_download.is_set(): data = source.read(READ_CHUNK_SIZE_BYTES) if data: destination.write(data) # make sure everything is written to disk now # https://docs.python.org/3/library/os.html#os.fsync destination.flush() os.fsync(destination) downloaded_bytes = os.fstat(destination.fileno()).st_size logger.debug(f"Downloaded {downloaded_bytes} bytes.") update_job_progress( JobProgressStatus.DOWNLOAD_PROGRESS.value, message=json.dumps({"downloaded_bytes": downloaded_bytes}), ) else: done = True if stop_download.is_set(): logger.info(f"Download stopped. Removing {job.filepath}.") os.remove(job.filepath) if done: logger.info(f"Download completed.") publish(pysm.Event(DOWNLOAD_COMPLETED, **{JOB: job})) except HTTPError as http_error: if http_error.status == 416: publish(pysm.Event(DOWNLOAD_COMPLETED, **{JOB: job})) elif http_error.status == 403: logger.warning("URL has expired. Starting over.") update_job_progress(JobProgressStatus.DOWNLOAD_INTERRUPT.value) publish(pysm.Event(DOWNLOAD_INTERRUPTED)) else: logger.error(f"HTTPError {http_error.status}: {http_error.reason}.") raise http_error except Exception as exception: if type(exception) not in RETRYABLE_EXCEPTIONS: # see issue #19, instead of hanging on unhanded exception, # we try to improve the situation by just starting over # since we don't have any better error recovery strategy. logger.exception("Unhandled failure. Starting over.") update_job_progress(JobProgressStatus.DOWNLOAD_INTERRUPT.value) publish(pysm.Event(DOWNLOAD_INTERRUPTED)) else: # let backoff.on_exception handle retry # for this exception raise exception
def on_job_cancelled(self, state, event): self.stop_hooks() self.stop_download.set() self.publish(pysm.Event(DOWNLOAD_INTERRUPTED))
def kein_finale(self, state, event): if len(self._spielerinnen_unique) == 1: self.root_machine.dispatch(pysm.Event(events.FERTIG_HALBZEIT))
def _publish(inbox, hook, status, message): inbox.put( pysm.Event( HOOK, **{HOOK_COMMAND: hook, HOOK_STATUS: status, HOOK_MESSAGE: message} ) )