Submit
Path:
~
/
/
opt
/
psa
/
admin
/
plib
/
modules
/
site-import
/
backend
/
lib
/
python
/
parallels
/
plesk
/
source
/
plesk
/
pmm_agent
/
File Content:
windows.py
import os import ntpath from xml.etree import ElementTree from parallels.core.logging import get_logger from parallels.core import messages from parallels.core.runners.base import BaseRunner from parallels.core.runners.entities import ExecutionOptions from parallels.core.runners.exceptions.directory_remove_exception import DirectoryRemoveException from parallels.core.utils.download_utils import download from parallels.core import MigrationNoContextError from parallels.core.utils import get_agents_base_path from parallels.core.utils.plesk_utils import get_windows_psa_database_name, get_windows_psa_database_login, \ get_windows_psa_database_port, get_windows_psa_database_host from parallels.core.utils.yaml_utils import read_yaml, write_yaml from parallels.core.utils.pmm.agent import PmmMigrationAgentBase, DumpAll, DumpSelected from parallels.core.utils import windows_utils from parallels.core.utils.windows_utils import path_join as windows_path_join, get_from_registry, find_in_registry from parallels.core.utils.common import safe_string_repr, safe_format, cached from parallels.core.runners.exceptions.non_zero_exit_code import NonZeroExitCodeException from parallels.plesk.source.plesk.capability_dump.model.factory import create_plesk_capability_model from parallels.plesk.source.plesk.capability_dump.xml_converter import CapabilityXMLConverter from parallels.plesk.source.plesk.pmm_agent.hosting_description import HostingDescriptionAgent from parallels.plesk.source.plesk.shallow_dump.model.factory import create_plesk_shallow_model from parallels.plesk.source.plesk.shallow_dump.xml_converter import ShallowXMLConverter from parallels.core.utils import migrator_utils import parallels.plesk.source.plesk logger = get_logger(__name__) class WindowsPleskPmmMigrationAgent(PmmMigrationAgentBase): """Plesk migration agent for Windows.""" def execute_sql(self, query, query_args=None, log_output=True): """Execute SQL query on panel database Returns list of rows, each row is a dictionary. Query and query arguments: * execute_sql("SELECT * FROM clients WHERE type=%s AND country=%s", ['client', 'US']) :type query: str :type query_args: list | dict :type log_output: bool :rtype: list[dict] """ with self._source_server.runner() as runner: return runner.execute_sql(query, query_args, self._get_panel_db_connection_settings(), log_output) @cached def _get_panel_db_connection_settings(self): """Get connections settings to connect to panel database ('psa') as dictionary :rtype: dict """ with self._source_server.runner() as runner: return { 'host': get_windows_psa_database_host(runner), 'port': get_windows_psa_database_port(runner), 'user': get_windows_psa_database_login(runner), 'password': self._source_server.panel_admin_password, 'database': get_windows_psa_database_name(runner), } def _run(self, local_dump_path, local_log_filename, selection=DumpAll()): """Create migration dump and download it to session directory.""" logger.finfo(messages.UTILS_PMM_WINDOWS_AGENT_CREATE_MIGRATION_DUMP, dump_path=local_dump_path) if os.path.exists(local_dump_path): os.remove(local_dump_path) source_server = self._source_server source_plesk_version = source_server.plesk_version # For Plesk < 17.0 we create dump using backup agent # For Plesk >= 17.0 we create dump ourselves using hosting description format, # convert it to Plesk backup format and merge it with dump created with Plesk Backup if source_plesk_version < (17, 0): self._clean_source() source_server_dump_file_path = self._make_dump(selection) self._download_dump(source_server_dump_file_path, local_dump_path) self._clean_source() else: HostingDescriptionAgent(self).create_dump(local_dump_path, selection) def _is_dump_version_upgrade_required(self): """Whether dump agent creates dump in old format (< 17.0), and upgrade is required to use with target Plesk 18 :rtype: bool """ source_server = self._source_server source_plesk_version = source_server.plesk_version return source_plesk_version < (17, 0) def _run_shallow(self, local_dump_path): """Create a dump with simplified structure, useful for creation of migration list.""" logger.finfo(messages.UTILS_PMM_WINDOWS_AGENT_CREATE_SHALLOW_DUMP, dump_path=local_dump_path) source_plesk_version = self._source_server.plesk_version # The new schema needs support of SQL queries to MS SQL/Jet for Plesks < 12.5 if source_plesk_version < (12, 5): self._clean_source() self._make_shallow_dump() with self._source_server.runner() as runner: runner.get_file(self._source_shallow_dump_xml_file, local_dump_path) self._clean_source() else: shallow_model = create_plesk_shallow_model( self, self._source_server.plesk_version ) ShallowXMLConverter(shallow_model).write_xml(local_dump_path) def _run_capability(self, local_dump_path, local_log_filename, selection=DumpAll()): """Create capability dump and download it to session directory.""" logger.finfo(messages.UTILS_PMM_WINDOWS_AGENT_CREATE_CAPABILITY_DUMP, dump_path=local_dump_path) source_plesk_version = self._source_server.plesk_version # The new schema needs support of SQL queries to MS SQL/Jet for Plesks < 12.5 if source_plesk_version < (12, 5): self._clean_source() self._make_capability_dump(selection) with self._source_server.runner() as runner: runner.get_file(self._source_capability_dump_xml_file, local_dump_path) self._clean_source() else: capability_model = create_plesk_capability_model( self, self._source_server.plesk_version, selection ) CapabilityXMLConverter(capability_model).write_xml(local_dump_path) def _make_dump(self, selection=DumpAll()): """Run agent on source server, generate migration dump.""" with self._source_server.runner() as runner: assert isinstance(runner, BaseRunner) command, options = self._get_create_dump_command(runner, selection) runner.execute_command(command, options) if not self._source_server.plesk_major_version >= 9: self._convert_dump_to_plesk9() return self._source_converted_dump_path else: return self._source_configuration_dump_dir def _make_shallow_dump(self): """Run agent on source server, generate shallow migration dump.""" command, options = self._get_create_shallow_dump_command() with self._source_server.runner() as runner: assert isinstance(runner, BaseRunner) runner.execute_command(command, options) def _make_capability_dump(self, selection): """Run agent on source server, generate capability dump.""" with self._source_server.runner() as runner: assert isinstance(runner, BaseRunner) command, options = self._get_create_capability_dump_command(runner, selection) runner.execute_command(command, options) def _create_filter_file(self, runner, selection): migration_objects_list = ElementTree.Element("migration-objects-list") resellers = ElementTree.SubElement(migration_objects_list, "resellers") for reseller in selection.resellers: ElementTree.SubElement(resellers, "reseller").text = reseller clients = ElementTree.SubElement(migration_objects_list, "clients") for client in selection.clients: ElementTree.SubElement(clients, "client").text = client domains = ElementTree.SubElement(migration_objects_list, "domains") for domain in selection.domains: ElementTree.SubElement(domains, "domain").text = domain filter_filename = self._source_server.get_session_file_path("filter.xml") runner.upload_file_content( filter_filename, ElementTree.tostring(migration_objects_list, 'utf-8', 'xml') ) return filter_filename def _get_create_dump_command(self, runner, selection=DumpAll()): if isinstance(selection, DumpSelected): filter_file = self._create_filter_file(runner, selection) else: filter_file = "" options = { 'agent_script': self._get_agent_path(), 'dump_command': '--create-full-migration-dump', 'filter_file': filter_file, 'pmm_temp_dir': self._source_pmm_temp_dir, 'configuration_dump_dir': self._source_configuration_dump_dir, } command = '{agent_script} {dump_command} {filter_file} {pmm_temp_dir} {configuration_dump_dir}' return command, options def _get_create_shallow_dump_command(self): options = { 'agent_script': self._get_agent_path(), 'dump_command': '--create-shallow-migration-dump', 'pmm_temp_dir': self._source_pmm_temp_dir, 'shallow_dump_xml_file': self._source_shallow_dump_xml_file } command = "{agent_script} {dump_command} {pmm_temp_dir} {shallow_dump_xml_file}" return command, options def _get_create_capability_dump_command(self, runner, selection): if isinstance(selection, DumpSelected): filter_file = self._create_filter_file(runner, selection) else: filter_file = "" options = { 'agent_script': self._get_agent_path(), 'dump_command': '--create-capability-info-dump', 'pmm_temp_dir': self._source_pmm_temp_dir, 'capability_dump_xml_file': self._source_capability_dump_xml_file, 'filter_file': filter_file } command = "{agent_script} {dump_command} {filter_file} {pmm_temp_dir} {capability_dump_xml_file}" return command, options def _download_dump(self, remote_dump_path, local_dump_path): """Download migration dump files as a ZIP archive with specified name. """ logger.info(messages.DOWNLOADING_MIGRATION_DUMP_S % (remote_dump_path,)) is_zipfile = remote_dump_path[-4:] == '.zip' if is_zipfile: zip_filename = remote_dump_path with self._source_server.runner() as runner: runner.get_file(zip_filename, local_dump_path) else: with self._source_server.runner() as runner: runner.download_directory_as_zip(remote_dump_path, local_dump_path) def _get_agent_path(self): return ntpath.join(self._get_base_path(), 'WINAgentMng.exe') def _deploy(self): if self._source_server.plesk_version >= (17, 0): return if self._get_base_path() is None: with self._source_server.runner() as runner: assert isinstance(runner, BaseRunner) storage_path = self._get_installer_path_in_storage() if os.path.exists(storage_path) is False: # download installer and save it into storage storage_url = 'http://autoinstall.plesk.com/panel-migrator/agents/%s' % ( self._agent_installer_filename ) logger.info(messages.DOWNLOAD_DUMP_AGENT.format(url=storage_url)) download(storage_url, storage_path) # upload installer installer_path = self._source_server.get_session_file_path(self._agent_installer_filename) logger.info(messages.UPLOAD_DUMP_AGENT.format( path=installer_path, server=self._source_server.description()) ) runner.upload_file(storage_path, installer_path) self._check_requirements(runner) possible_registry_keys = [ 'HKLM\SOFTWARE\Wow6432Node\PleskMigrator\Agent', 'HKLM\SOFTWARE\Wow6432Node\PanelMigrator\Agent', 'HKLM\SOFTWARE\PleskMigrator\Agent', 'HKLM\SOFTWARE\PanelMigrator\Agent' ] # check if agent was already installed and remove it base_path = get_from_registry(runner, possible_registry_keys, 'INSTALLDIR') if base_path is not None: runner.execute_command( 'msiexec /x {installer_path} /qn', dict(installer_path=installer_path), ExecutionOptions(ignore_exit_code=True, output_encoding=runner.native_output_encoding) ) # install agent and store path in session logger.info(messages.INSTALL_DUMP_AGENT.format(server=self._source_server.description())) cmd = 'msiexec /i {installer_path} /qn' execution_results = runner.execute_command( cmd, dict(installer_path=installer_path), ExecutionOptions(ignore_exit_code=True, output_encoding=runner.native_output_encoding) ) if execution_results.exit_code != 0: if execution_results.exit_code == 1618: raise NonZeroExitCodeException( messages.UNABLE_TO_INSTALL_DUMP_AGENT_ANOTHER_INSTALLER % ( self._source_server.description() ), stdout=execution_results.stdout, stderr=execution_results.stderr, exit_code=execution_results.exit_code ) else: raise NonZeroExitCodeException( messages.INSTALL_COMMAND_FAILED_WITH_EXIT_CODE % ( cmd, execution_results.exit_code, safe_string_repr(execution_results.stdout.replace('\x00', '')), safe_string_repr(execution_results.stderr) ), stdout=execution_results.stdout, stderr=execution_results.stderr, exit_code=execution_results.exit_code ) base_path = get_from_registry(runner, possible_registry_keys, 'INSTALLDIR') if base_path is not None: write_yaml( self.global_context.session_files.get_path_to_migration_agent(self._source_server.ip()), base_path ) return base_path def _check_requirements(self, runner): """ Check that source server meet requirements to deploy and run Panel Migrator Agent, raise exception otherwise :type runner: parallels.core.run_command.BaseRunner """ def _raise_exception(): raise MigrationNoContextError( messages.UNABLE_USE_PANEL_MIGRATOR_AGENT_SERVER.format( server=self._source_server.description() ) ) if not find_in_registry(runner, ['HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework'], 'v4.*'): _raise_exception() def _get_base_path(self): path = read_yaml( self.global_context.session_files.get_path_to_migration_agent(self._source_server.ip()), True ) if path is not None: # If agent was removed, but we have information in session directory that it was installed, # then try to install it again with self._source_server.runner() as runner: if not runner.file_exists(path): path = None return path def _get_installer_path_in_storage(self): return os.path.join(get_agents_base_path(), self._agent_installer_filename) @property def _agent_installer_filename(self): return 'panel-migrator-agent-12.5.msi' def _convert_dump_to_plesk9(self): """Convert Windows backup format remotely (on the source server).""" logger.info(messages.CONVERT_PLESK_DUMP_PLESK_FORMAT) with self._source_server.runner() as runner: assert isinstance(runner, BaseRunner) dump_archive = ntpath.join( self._source_configuration_dump_dir, 'psaDump.zip' # this name is hardcoded at agent side ) if not runner.file_exists(dump_archive): raise MigrationNoContextError(safe_format( messages.PLESK_8_DUMP_FILE_NOT_FOUND, dump_archive=dump_archive )) converter_path = self.upload_plesk9_converter(runner) runner.execute_command( u'{converter_path} --source={source_dump_archive} --destination={converted_dump_path}', dict( converter_path=converter_path, source_dump_archive=dump_archive, converted_dump_path=self._source_converted_dump_path ) ) def check(self): raise NotImplementedError() def uninstall(self): """Windows agent uninstallation is not supported.""" pass def upload_plesk9_converter(self, runner): """Upload Plesk dump format converter to the source server. :type runner: parallels.core.runners.base.BaseRunner """ source_plesk_dir = self._source_server.plesk_dir runner.mkdir(u'%s\\PMM' % (source_plesk_dir,)) converter = u'admin/bin/pre9-backup-convert.exe' converter_xsl = u'PMM/migration-dump-convert.xsl' for path in [converter, converter_xsl]: local_path = migrator_utils.get_package_extras_file_path( parallels.plesk.source.plesk, os.path.basename(path) ) remote_path = os.path.join(source_plesk_dir.rstrip('\\'), path) runner.upload_file(local_path, remote_path) upload_path = windows_path_join(source_plesk_dir, windows_utils.to_cmd(converter)) logger.debug(messages.DEBUG_UPLOAD_CONVERTER, upload_path) return upload_path def _clean_source(self): """Remove all temporary files from the source server""" with self._source_server.runner() as runner: try: runner.remove_directory(self._source_pmm_temp_dir) except DirectoryRemoveException as e: # print warning, try to continue anyway logger.warning(safe_string_repr(e)) try: runner.remove_directory(self._source_converted_dump_path) except DirectoryRemoveException as e: # print warning, try to continue anyway logger.warning(safe_string_repr(e)) @property def _source_pmm_temp_dir(self): return self._source_server.get_session_file_path('pmm-temp') @property def _source_capability_dump_xml_file(self): return self._source_server.get_session_file_path(r'pmm-temp\capability.xml') @property def _source_shallow_dump_xml_file(self): return self._source_server.get_session_file_path(r'pmm-temp\shallow.xml') @property def _source_configuration_dump_dir(self): return self._source_server.get_session_file_path(r'pmm-temp\dump') @property def _source_converted_dump_path(self): # Workaround for issue PMT-436: # make path to backup file as short as possible, but root cause, as we suppose, # should be fixed in plesk 8 backup converter return self._source_server.get_session_file_path('c')
Submit
FILE
FOLDER
INFO
Name
Size
Permission
Action
__pycache__
---
0770
__init__.py
0 bytes
0644
hosting_description.py
20011 bytes
0644
unix.py
13809 bytes
0644
utils.py
2002 bytes
0644
windows.py
20238 bytes
0644
N4ST4R_ID | Naxtarrr