def test_wrong_uninstall_section_should_fail(self): """Create package extra uninstallation options manager with wrong unistall section.""" manifest = self.generate_mock_manifest( cfg={EXTCFG_SECTION.UNINSTALL: ''}) ext_manager = PkgInstExtrasManager(manifest) with pytest.raises(exceptions.InstExtrasManagerConfigError): ext_manager.handle_uninstall_extras()
def test_wrong_install_extras_section_should_fail(self): """Create package extra installation options manager with wrong extras configuration section.""" manifest = self.generate_mock_manifest( cfg={EXTCFG_SECTION.INSTALL: { EXTCFG_OPTION.EXEC_EXT_CMD: '', }}) ext_manager = PkgInstExtrasManager(manifest) with pytest.raises(exceptions.InstExtrasManagerConfigError): ext_manager.handle_install_extras()
def test_uninstall_subprocess_error_should_fail(self, *args): """Raise exception during installation process command execution.""" manifest = self.generate_mock_manifest(cfg={ EXTCFG_SECTION.UNINSTALL: { EXTCFG_OPTION.EXEC_EXT_CMD: ['command'], } }) ext_manager = PkgInstExtrasManager(manifest) with pytest.raises(exceptions.InstExtrasManagerError): ext_manager.handle_uninstall_extras()
def test_install_should_call_subprocess_run(self, mock_subprocess): """Raise exception during installation process command execution.""" manifest = self.generate_mock_manifest(cfg={ EXTCFG_SECTION.INSTALL: { EXTCFG_OPTION.EXEC_EXT_CMD: ['command'], } }) ext_manager = PkgInstExtrasManager(manifest) ext_manager.handle_install_extras() mock_subprocess.assert_called_with('command', check=True, stderr=-1, stdout=-1, timeout=90, universal_newlines=True)
def __init__(self, pkg_id=None, istor_nodes=None, cluster_conf=None, manifest=None): """Constructor. :param pkg_id: PackageId, package ID :param istor_nodes: IStorNodes, DC/OS installation storage nodes (set of pathlib.Path objects) :param cluster_conf: dict, configparser.ConfigParser.read_dict() compatible data. DC/OS cluster setup parameters :param manifest: PackageManifest, DC/OS package manifest object """ if manifest is not None: assert isinstance(manifest, PackageManifest), ( f'Argument: manifest:' f' Got {type(manifest).__name__} instead of PackageManifest') self.manifest = manifest else: self.manifest = PackageManifest(pkg_id, istor_nodes, cluster_conf) if self.manifest.pkg_extcfg: self.ext_manager = PkgInstExtrasManager( ext_conf=self.manifest.pkg_extcfg) else: self.ext_manager = None if self.manifest.pkg_svccfg: self.svc_manager = WinSvcManagerNSSM( svc_conf=self.manifest.pkg_svccfg) else: self.svc_manager = None
def __init__(self, pkg_id: PackageId = None, istor_nodes: IStorNodes = None, cluster_conf: dict = None, extra_context: dict = None, manifest: PackageManifest = None): """Constructor. :param pkg_id: PackageId, package ID :param istor_nodes: IStorNodes, DC/OS installation storage nodes (set of pathlib.Path objects) :param cluster_conf: dict, configparser.ConfigParser.read_dict() compatible data. DC/OS cluster setup parameters :param extra_context: dict, extra 'key=value' data to be added to the resource rendering context :param manifest: PackageManifest, DC/OS package manifest object """ self.msg_src = self.__class__.__name__ if manifest is None: manifest = PackageManifest(pkg_id=pkg_id, istor_nodes=istor_nodes, cluster_conf=cluster_conf, extra_context=extra_context) self.manifest = manifest LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}: Manifest:' f' {self.manifest}') self.cfg_manager = PkgConfManager(pkg_manifest=self.manifest) LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}:' f' Package configuration manager: {self.cfg_manager}') if self.manifest.pkg_extcfg: self.ext_manager = PkgInstExtrasManager(pkg_manifest=self.manifest) else: self.ext_manager = None LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}:' f' Installation extras manager: {self.ext_manager}') if self.manifest.pkg_svccfg: self.svc_manager = WinSvcManagerNSSM( svc_conf=self.manifest.pkg_svccfg) else: self.svc_manager = None LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}:' f' Service manager: {self.svc_manager}')
def test_to_string_should_convert_manifest_configuration(self): """Create package extra installation options manager.""" manifest = self.generate_mock_manifest(cfg={'opt': 'val'}) ext_manager = PkgInstExtrasManager(manifest) assert str(ext_manager) == '{\'ext_conf\': {\'opt\': \'val\'}}'
def test_wrong_manifest_type_should_fail(self): """Create package extra installation options manager with empty manifest.""" with pytest.raises(AssertionError): PkgInstExtrasManager(pkg_manifest=None)
class Package: """Package manager.""" def __init__(self, pkg_id: PackageId = None, istor_nodes: IStorNodes = None, cluster_conf: dict = None, extra_context: dict = None, manifest: PackageManifest = None): """Constructor. :param pkg_id: PackageId, package ID :param istor_nodes: IStorNodes, DC/OS installation storage nodes (set of pathlib.Path objects) :param cluster_conf: dict, configparser.ConfigParser.read_dict() compatible data. DC/OS cluster setup parameters :param extra_context: dict, extra 'key=value' data to be added to the resource rendering context :param manifest: PackageManifest, DC/OS package manifest object """ self.msg_src = self.__class__.__name__ if manifest is None: manifest = PackageManifest( pkg_id=pkg_id, istor_nodes=istor_nodes, cluster_conf=cluster_conf, extra_context=extra_context ) self.manifest = manifest self.cfg_manager = PkgConfManager(pkg_manifest=self.manifest) if self.manifest.pkg_extcfg: self.ext_manager = PkgInstExtrasManager(pkg_manifest=self.manifest) else: self.ext_manager = None LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}:' f' Installation extras manager: {self.ext_manager}') if self.manifest.pkg_svccfg: self.svc_manager = WinSvcManagerNSSM( svc_conf=self.manifest.pkg_svccfg ) else: self.svc_manager = None LOG.debug(f'{self.msg_src}: {self.manifest.pkg_id.pkg_id}:' f' Service manager: {self.svc_manager}') @property def id(self): """""" return self.manifest.pkg_id def handle_config_setup(self, mheading: str=None): """Execute steps on setting up package configuration files. :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 LOG.debug(f'{mheading}: Setup configuration: ...') try: self.cfg_manager.setup_conf() except cfgm_exc.PkgConfNotFoundError as e: LOG.debug(f'{mheading}: Setup configuration: NOP') else: LOG.debug(f'{mheading}: Setup configuration: OK') def handle_inst_extras(self, mheading: str=None): """Process package's extra installation options. :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 if self.ext_manager: LOG.debug(f'{mheading}: Handle extra installation options: ...') self.ext_manager.handle_install_extras() LOG.debug(f'{mheading}: Handle extra installation options: OK') else: LOG.debug(f'{mheading}: Handle extra installation options: NOP') def handle_uninst_extras(self, mheading: str=None): """Process package's extra uninstall options. :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 if self.ext_manager: LOG.debug(f'{mheading}: Handle extra uninstall options: ...') self.ext_manager.handle_uninstall_extras() LOG.debug(f'{mheading}: Handle extra uninstall options: OK') else: LOG.debug(f'{mheading}: Handle extra uninstall options: NOP') def handle_svc_setup(self, mheading: str=None): """Execute steps on package's service setup. :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 no_svc_marker = 'service does not exist as an installed service.' if self.svc_manager: svc_name = self.svc_manager.svc_name LOG.debug(f'{mheading}: Setup service: {svc_name}: ...') try: ret_code, stdout, stderr = self.svc_manager.status() except svcm_exc.ServiceManagerCommandError as e: exc_msg_line = str(e).replace('\n', '').strip() LOG.debug(f'{mheading}: Setup service: {svc_name}:' f' Get initial service status: {exc_msg_line}') if not exc_msg_line.endswith(no_svc_marker): err_msg = f'{mheading}: Setup service: {svc_name}:' \ f' Get initial service status: {e}' raise svcm_exc.ServiceSetupError(err_msg) from e # Setup a service try: self.svc_manager.setup() except svcm_exc.ServiceManagerCommandError as e: err_msg = f'{mheading}: Setup service: {svc_name}: {e}' raise svcm_exc.ServiceSetupError(err_msg) from e else: LOG.debug( f'{mheading}: Setup service: {svc_name}: Get initial' f' service status: stdout[{stdout}] stderr[{stderr}]' ) svc_status = str(stdout).strip().rstrip('\n') # Wipe existing service try: if svc_status != SVC_STATUS.STOPPED: self.svc_manager.stop() self.svc_manager.remove() LOG.debug(f'{mheading}: Wipe existing service:' f' {svc_name}: OK') except svcm_exc.ServiceManagerCommandError as e: err_msg = (f'{mheading}: Wipe existing service:' f' {svc_name}: {e}') raise svcm_exc.ServiceSetupError(err_msg) from e # Setup a replacement service try: self.svc_manager.setup() ret_code, stdout, stderr = (self.svc_manager.status()) svc_status = str(stdout).strip().rstrip('\n') except svcm_exc.ServiceManagerCommandError as e: err_msg = (f'{mheading}: Setup replacement service:' f' {svc_name}: {e}') raise svcm_exc.ServiceSetupError(err_msg) from e else: if svc_status != SVC_STATUS.STOPPED: err_msg = ( f'{mheading}: Setup replacement service:' f' {svc_name}: Invalid status: {svc_status}' ) raise svcm_exc.ServiceSetupError(err_msg) LOG.debug(f'{mheading}: Setup service: {svc_name}: OK') else: LOG.debug(f'{mheading}: Setup service: NOP') def handle_svc_wipe(self, mheading: str=None): """Execute steps on package's service wipe off. :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 no_svc_marker = 'service does not exist as an installed service.' if self.svc_manager: svc_name = self.svc_manager.svc_name LOG.debug(f'{mheading}: Wipe service: {svc_name}: ...') try: ret_code, stdout, stderr = self.svc_manager.status() except svcm_exc.ServiceManagerCommandError as e: exc_msg_line = str(e).replace('\n', '').strip() LOG.debug(f'{mheading}: Wipe service: {svc_name}:' f' Get initial service status: {exc_msg_line}') if not exc_msg_line.endswith(no_svc_marker): err_msg = f'{mheading}: Wipe service: {svc_name}: {e}' raise svcm_exc.ServiceWipeError(err_msg) from e LOG.debug(f'{mheading}: Wipe service: NOP') else: LOG.debug( f'{mheading}: Wipe service: {svc_name}: Get initial' f' service status: stdout[{stdout}] stderr[{stderr}]' ) svc_status = str(stdout).strip().rstrip('\n') # Try to remove existing service try: if svc_status != SVC_STATUS.STOPPED: self.svc_manager.stop() self.svc_manager.remove() LOG.debug(f'{mheading}: Wipe service: {svc_name}: OK') except svcm_exc.ServiceManagerCommandError as e: err_msg = (f'{mheading}: Wipe existing service:' f' {svc_name}: {e}') raise svcm_exc.ServiceWipeError(err_msg) from e else: LOG.debug(f'{mheading}: Wipe service: NOP') def handle_svc_start(self, mheading: str=None): """Execute steps on package's service start. :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 no_svc_marker = 'service does not exist as an installed service.' # TODO: Move stuff from the CmdStart.service_start() method here. # Overall design of this method should resemble one of the # Package.handle_svc_stop() method. def handle_svc_stop(self, mheading: str=None): """Execute steps on package's service stop. :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 no_svc_marker = 'service does not exist as an installed service.' if self.svc_manager: svc_name = self.svc_manager.svc_name LOG.debug(f'{mheading}: Stop service: {svc_name}: ...') try: ret_code, stdout, stderr = self.svc_manager.status() except svcm_exc.ServiceManagerCommandError as e: exc_msg_line = str(e).replace('\n', '').strip() LOG.debug(f'{mheading}: Stop service: {svc_name}:' f' Get initial service status: {exc_msg_line}') if not exc_msg_line.endswith(no_svc_marker): err_msg = f'{mheading}: Stop service: {svc_name}: {e}' raise svcm_exc.ServiceStopError(err_msg) from e LOG.debug(f'{mheading}: Stop service: NOP') else: LOG.debug( f'{mheading}: Stop service: {svc_name}: Get initial' f' service status: stdout[{stdout}] stderr[{stderr}]' ) svc_status = str(stdout).strip().rstrip('\n') # Stop existing service try: if svc_status != SVC_STATUS.STOPPED: self.svc_manager.stop() LOG.debug(f'{mheading}: Stop service: {svc_name}: OK') except svcm_exc.ServiceManagerCommandError as e: err_msg = f'{mheading}: Stop service: {svc_name}: {e}' raise svcm_exc.ServiceStopError(err_msg) from e else: LOG.debug(f'{mheading}: Stop service: NOP') 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 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 delete_manifest(self, mheading: str=None, dpath: Path=None): """Delete package's manifest from filesystem. :param mheading: str, descriptive heading to be added to error/log messages :param dpath: Path, absolute path to the host directory where to delete from """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base try: self.manifest.delete(dpath) except (OSError, RuntimeError, cr_exc.RCError) as e: err_msg = f'{mheading}: Deregister package: {self.id}: {e}' raise cr_exc.RCError(err_msg) from e