def rollback() -> None: """""" for path in rollback_path_list: # type: Path # Remove an existing DC/OS installation storage element try: cm_utl.rmdir(path=str(path), recursive=True) except (OSError, RuntimeError) as e: LOG.error(f'{self.__class__.__name__}: Construction:' f' Rollback: {path}: {type(e).__name__}: {e}')
def remove_package(self, pkg_id: PackageId) -> None: """Remove a package from the local package repository. :param pkg_id: PackageId, package ID """ try: pkg_dpath = self.pkgrepo_dpath.joinpath(pkg_id.pkg_id) cm_utl.rmdir(str(pkg_dpath), recursive=True) except (OSError, RuntimeError) as e: raise cr_exc.RCRemoveError( f'Package {pkg_id.pkg_id}: {type(e).__name__}: {e}')
def destruct(self) -> None: """Remove entire existing DC/OS installation storage.""" for path in self.istor_nodes[1:]: # type: Path if path.is_absolute() and path.is_dir(): try: cm_utl.rmdir(path=str(path), recursive=True) LOG.debug(f'{self.__class__.__name__}: Destruction:' f' Remove directory: {path}') except (OSError, RuntimeError) as e: raise cr_exc.InstallationStorageError( f'Destruction: Remove directory: {path}:' f' {type(e).__name__}: {e}')
def save_manifest(self, mheading: str=None, dpath: Path=None): """Save package's manifest to filesystem. :param mheading: str, descriptive heading to be added to error/log messages :param dpath: Path, absolute path to the host directory where to save to """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base try: cm_utl.rmdir(str(dpath), recursive=True) dpath.mkdir(parents=True) self.manifest.save(dpath) except (OSError, RuntimeError, cr_exc.RCError) as e: err_msg = f'{mheading}: Register package: {self.id}: {e}' raise cr_exc.RCError(err_msg) from e
def _handle_teardown(self): """Teardown the currently installed DC/OS.""" mheading = f'{self.msg_src}: Execute' pkg_manifests = ( self.config.inst_storage.get_pkgactive(PackageManifest.load) ) packages_bulk = { m.pkg_id.pkg_name: Package(manifest=m) for m in pkg_manifests } iroot_dpath = self.config.inst_storage.root_dpath itmp_dpath = self.config.inst_storage.tmp_dpath pkgactive_old_dpath = itmp_dpath.joinpath( f'{storage.DCOS_PKGACTIVE_DPATH_DFT}.old' ) sh_conf_dname = storage.DCOS_INST_CFG_DPATH_DFT sh_exec_dname = storage.DCOS_INST_BIN_DPATH_DFT sh_lib__dname = storage.DCOS_INST_LIB_DPATH_DFT # Teardown installed packages for package in cr_utl.pkg_sort_by_deps(packages_bulk): package.handle_svc_wipe(mheading) package.handle_uninst_extras(mheading) package.save_manifest(mheading, pkgactive_old_dpath) package.delete_manifest(mheading) # Remove/preserve shared directories for dname in sh_conf_dname, sh_exec_dname, sh_lib__dname: active_dpath = iroot_dpath.joinpath(dname) preserve_dpath = itmp_dpath.joinpath(f'{dname}.old') try: cm_utl.rmdir(str(preserve_dpath), recursive=True) active_dpath.rename(preserve_dpath) except (OSError, RuntimeError) as e: err_msg = (f'{mheading}: Preserve shared directory:' f' {active_dpath}: {type(e).__name__}: {e}') raise cr_exc.RCError(err_msg) from e LOG.debug(f'{mheading}: Preserve shared directory: {active_dpath}:' f' {preserve_dpath}')
def handle_vardata_wipe(self, mheading: str=None): """Execute steps on wiping of package's variable data. :param mheading: str, descriptive heading to be added to error/log messages """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base work_dpath = getattr(self.manifest.istor_nodes, ISTOR_NODE.WORK) run_dpath = getattr(self.manifest.istor_nodes, ISTOR_NODE.RUN) for host_dpath in work_dpath, run_dpath: dpath = host_dpath.joinpath(self.id.pkg_name) try: cm_utl.rmdir(str(dpath), recursive=True) dpath.mkdir(parents=True, exist_ok=True) LOG.debug(f'{mheading}: Wipe variable data: {dpath}: OK') except (OSError, RuntimeError) as e: err_msg = (f'{mheading}: Wipe variable data: {dpath}:' f' {type(e).__name__}: {e}') raise cr_exc.RCError(err_msg) from e
def _handle_teardown_post(self): """Perform extra steps on cleaning up unplanned (diverging from initial winpanda design and so, not removed by normal teardown procedure) DC/OS installation leftovers (see the CmdSetup._handle_pkg_dir_setup() and workaround for dcos-diagnostics part in the InstallationStorage.add_package()). """ mheading = f'{self.msg_src}: Execute' LOG.debug(f'{mheading}: After steps: ...') iroot_dpath = self.config.inst_storage.root_dpath ivar_dpath = self.config.inst_storage.var_dpath itmp_dpath = self.config.inst_storage.tmp_dpath wipe_dirs = [ iroot_dpath.joinpath('include'), iroot_dpath.joinpath('mesos-logs'), ivar_dpath.joinpath('lib'), ] for dpath in wipe_dirs: try: cm_utl.rmdir(str(dpath), recursive=True) LOG.debug(f'{mheading}: After steps: Remove dir: {dpath}: OK') except (OSError, RuntimeError) as e: LOG.warning(f'{mheading}: After steps: Remove dir: {dpath}:' f' {type(e).__name__}: {e}') wipe_files = [ iroot_dpath.joinpath('dcos-diagnostics.exe'), iroot_dpath.joinpath('servicelist.txt'), ] for fpath in wipe_files: try: fpath.unlink() LOG.debug(f'{mheading}: After steps: Remove file: {fpath}: OK') except (OSError, RuntimeError) as e: LOG.warning(f'{mheading}: After steps: Remove file: {fpath}:' f' {type(e).__name__}: {e}') # Restore objects created/populated by entities/processes outside # of winpanda routines, but required for winpanda to do it's stuff. restore_dirs = [ iroot_dpath / 'etc', iroot_dpath / 'etc' / 'roles', ] for dpath in restore_dirs: try: dpath.mkdir(parents=True, exist_ok=True) LOG.debug(f'{mheading}: After steps: Restore dir: {dpath}: OK') except (OSError, RuntimeError) as e: LOG.warning(f'{mheading}: After steps: Restore dir: {dpath}:' f' {type(e).__name__}: {e}') restore_files = [ (itmp_dpath / 'etc.old' / 'cluster.conf', iroot_dpath / 'etc'), (itmp_dpath / 'etc.old' / 'paths.json', iroot_dpath / 'etc'), (itmp_dpath / 'etc.old' / 'roles' / 'slave', iroot_dpath / 'etc' / 'roles'), ] for fspec in restore_files: try: shutil.copy(str(fspec[0]), str(fspec[1]), follow_symlinks=False) LOG.debug( f'{mheading}: After steps: Restore file: {fspec}: OK') except (OSError, RuntimeError) as e: LOG.warning(f'{mheading}: After steps: Restore file: {fspec}:' f' {type(e).__name__}: {e}') LOG.debug(f'{mheading}: After steps: OK')
def construct(self, clean: bool = False) -> None: """Construct DC/OS installation storage. :param clean: boolean, create a clean FS folder structure, wiping out any possible leftovers, if True. Otherwise repair FS directory structure, creating any missed pieces, as required. """ clean_ready = self._inst_stor_is_clean_ready(clean=clean) LOG.debug(f'{self.__class__.__name__}:' f' Construction: Clean ready: {clean_ready}') rollback_path_list = [] # type: List def rollback() -> None: """""" for path in rollback_path_list: # type: Path # Remove an existing DC/OS installation storage element try: cm_utl.rmdir(path=str(path), recursive=True) except (OSError, RuntimeError) as e: LOG.error(f'{self.__class__.__name__}: Construction:' f' Rollback: {path}: {type(e).__name__}: {e}') for path in self.istor_nodes: # type: Path if path.exists(): if path.is_symlink(): rollback() raise cr_exc.InstallationStorageError( f'Construction: Symlink conflict: {path}' ) elif path.is_reserved(): rollback() raise cr_exc.InstallationStorageError( f'Construction: Reserved name conflict: {path}' ) elif not path.is_dir(): # Remove a file try: path.unlink() LOG.debug(f'{self.__class__.__name__}:' f' Construction: Auto-cleanup: File: {path}') except (OSError, RuntimeError) as e: rollback() raise cr_exc.InstallationStorageError( f'Construction: Auto-cleanup: File: {path}:' f' {type(e).__name__}: {e}' ) from e # Create a fresh DC/OS installation storage element try: path.mkdir(parents=True, exist_ok=True) LOG.debug(f'{self.__class__.__name__}: Construction:' f' Create directory: {path}') except (OSError, RuntimeError) as e: rollback() raise cr_exc.InstallationStorageError( f'Construction: Create directory: {path}:' f' {type(e).__name__}: {e}' ) from e elif clean is True: if clean_ready is True: # Remove an existing DC/OS installation storage element try: cm_utl.rmdir(path=str(path), recursive=True) LOG.debug(f'{self.__class__.__name__}:' f' Construction: Cleanup: {path}') except (OSError, RuntimeError) as e: rollback() raise cr_exc.InstallationStorageError( f'Construction: Cleanup: {path}:' f' {type(e).__name__}: {e}' ) from e # Create a fresh DC/OS installation storage element try: path.mkdir(parents=True, exist_ok=True) LOG.debug(f'{self.__class__.__name__}:' f' Construction: Create directory:' f' {path}') except (OSError, RuntimeError) as e: rollback() raise cr_exc.InstallationStorageError( f'Construction: Create directory: {path}:' f' {type(e).__name__}: {e}' ) from e else: rollback() raise cr_exc.InstallationStorageError( f'Construction: Not ready for cleanup : {path}' ) else: # Create a fresh DC/OS installation storage element try: path.mkdir(parents=True, exist_ok=True) rollback_path_list.append(path) LOG.debug(f'{self.__class__.__name__}: Construction:' f' Create directory: {path}') except (OSError, RuntimeError) as e: rollback() raise cr_exc.InstallationStorageError( f'Construction: Create directory: {path}:' f' {type(e).__name__}: {e}' ) from e
def test_rm_not_dir_should_fail(*args): """Try remove file and raise exception.""" with pytest.raises(OSError): utils.rmdir('/tmp/.000')
def test_rm_reserved_should_fail(*args): """Try remove reserved path and raise exception.""" with pytest.raises(OSError): utils.rmdir('/tmp/.000')
def test_rm_symlink_should_fail(*args): """Try remove symlink and raise exception.""" with pytest.raises(OSError): utils.rmdir('/tmp/.000')
def test_rm_nonexistent_dir_should_fail(*args): """Remove empty path without exceptions and None result.""" assert utils.rmdir('path') is None