D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
psa
/
admin
/
plib
/
modules
/
site-import
/
backend
/
lib
/
python
/
parallels
/
plesk
/
converter
/
Filename :
converter.py
back
Copy
from parallels.core.logging import get_logger from parallels.core import target_data_model as target_model from parallels.core.converter.business_objects.clients import ClientsConverter from parallels.core.converter.business_objects.common import \ index_plesk_backup_domain_objects, \ check_domain_conflicts, check_subscription_conflicts, \ check_client_contacts_matching_source_panels, \ check_client_contacts_matching_source_target, \ index_target_site_names, \ EntityConverter from parallels.core.registry import Registry from parallels.core.reports.model.issue import Issue from parallels.core.reports.model.report import Report from parallels.core.utils.common import group_by_id, find_first, default from parallels.plesk import messages logger = get_logger(__name__) class PleskConverter(object): """Generate model of subscriptions and customers ready to import to target panel. Converter takes the following information: - Plesk backup or other source of information about resellers, clients, subscriptions and domains on source servers - Information about objects that already exist on the target panel: resellers, plans, clients, domains and so on - Migration list, which contains list of customers and subscriptions to migrate, their mapping between each other, plans and resellers Converter: - Converts each customer and subscription we need to migrate to format ready to import to target panel (clients and subscriptions above hosting settings). - Performs conflict resolution, for example if client or subscriptions exists on multiple source panel, or already exists on target panel. - Performs assignment of subscriptions to clients and plans, clients to resellers according to migration list - Performs some basic pre-migration checks, for example that plan already exists on target panel. Result is a model ready to import to target panel. Converter is NOT responsible for hosting settings below subscriptions, like subdomains, or PHP settings. """ def __init__(self, target_existing_objects, entity_converter=None, encrypted_passwords_supported=False): self.target_reseller_plans = {} self.target_plans = {} self.target_resellers = {} self.target_clients = {} self.target_site_names = {} self.existing_objects = target_existing_objects if entity_converter is not None: self.entity_converter = entity_converter else: self.entity_converter = EntityConverter(self.existing_objects) self.raw_target_clients = { None: ClientsConverter.create_fake_client(None) # admin fake client } # these special clients are included here, but not distinguished from other - regular - clients self.raw_target_subscriptions = {} self.existing_site_names = index_target_site_names(self.existing_objects) self._encrypted_passwords_supported = encrypted_passwords_supported def convert_dumps( self, sources_info, plain_report, subscriptions_mapping, customers_mapping, reseller_plans, hosting_plans, resellers, password_holder ): """ :type sources_info: list[parallels.core.global_context.SourceInfo] :type plain_report: parallels.core.reports.plain_report.PlainReport :type subscriptions_mapping: dict[str, parallels.core.migration_list.entities.subscription_info.SubscriptionMappingInfo] :type customers_mapping: dict[str, str] :type reseller_plans: list[parallels.core.target_data_model.ResellerPlan] :type hosting_plans: dict[str|None, list[parallels.core.target_data_model.HostingPlan]] :type resellers: list[parallels.core.target_data_model.Reseller] :type password_holder: parallels.core.utils.password_holder.PasswordHolder """ context = Registry.get_instance().get_context() # store reseller plans in model self.target_reseller_plans = group_by_id(reseller_plans, lambda p: p.name) # store hosting plans owned by administrator in model self.target_plans = group_by_id(hosting_plans.get(None, []), lambda p: p.name) # store resellers in model self.target_resellers = group_by_id(resellers, lambda r: r.login) all_source_client_logins = set() for source_info in sources_info: all_source_client_logins.update({ client.login for client in source_info.load_raw_dump().iter_all_clients() }) for source_info in sources_info: logger.debug(messages.CONVERT_DUMP) is_windows = source_info.is_windows if source_info.is_server else context.conn.target.is_windows self._convert_dump( source_info.id, source_info.load_raw_dump(), plain_report, is_windows, subscriptions_mapping, customers_mapping, hosting_plans, password_holder, all_source_client_logins ) # Check that each subscription from migration list actually exists in some backup for subscription in subscriptions_mapping: subscription_exists = any( source_info.load_raw_dump().has_subscription(subscription) for source_info in sources_info ) if not subscription_exists: plain_report.get_root_report().add_issue( "subscription_does_not_exist", Issue.SEVERITY_WARNING, messages.SUBSCRIPTION_DOES_NOT_EXIST_ON_SOURCE.format(subscription=subscription), ) def get_target_model(self): return target_model.Model( reseller_plans=self.target_reseller_plans, plans=self.target_plans, resellers=self.target_resellers, clients=self.target_clients ) def _convert_dump( self, plesk_id, backup, plain_report, is_windows, subscriptions_mapping, customers_mapping, hosting_plans, password_holder, all_source_client_logins=None ): logger.debug(messages.CREATE_TARGET_MODEL_OBJECTS) all_source_client_logins = default(all_source_client_logins, set()) # 1. create model objects (clients and subscriptions), but do not add them to model yet for subscription, _, reseller in backup.iter_all_subscriptions_with_owner_and_reseller(): self._add_subscription(subscription, reseller, plesk_id, is_windows, plain_report, backup) # Add clients from source server for client, reseller in backup.iter_all_clients_with_owner(): if client.login in customers_mapping: self._add_client( client, reseller, plesk_id, plain_report, password_holder, customers_mapping[client.login], backup.admin_descriptions ) # Add clients that exist on target server only, and do not exist on any of source servers for client_login, client in self.existing_objects.customers.items(): if client_login not in self.raw_target_clients and client_login not in all_source_client_logins: self.raw_target_clients[client_login] = EntityConverter.create_client_stub_from_existing_client(client) for reseller in self.target_resellers: self.raw_target_clients[reseller] = ClientsConverter.create_fake_client(reseller) # 2. link model objects # 2.1 plans <- subscription, according to migration list # dict(owner => set(plan names)), where owner is reseller login or None in case of admin plan_names_by_owner = self._get_plans_by_owners(hosting_plans) plan_addon_names_by_owner = self._get_plan_addons_by_owners(hosting_plans) for subscription in self.raw_target_subscriptions.values(): if subscription.source == plesk_id: if subscription.name in subscriptions_mapping: plan_name = subscriptions_mapping[subscription.name].plan owner_login = subscriptions_mapping[subscription.name].owner if owner_login is not None: if owner_login in customers_mapping: # owner is a customer reseller_login = customers_mapping.get(owner_login) else: reseller_login = owner_login # owner is reseller else: reseller_login = None # owner is administrator if plan_name is not None: subscription_report = plain_report.get_subscription_report(subscription.name) self._check_plan_exists( plan_names_by_owner.get(reseller_login, dict()), plan_name, reseller_login, subscription_report ) if not subscription_report.has_errors(): subscription.plan_name = plan_name # take addon plans mapping for addon_plan_name in subscriptions_mapping[subscription.name].addon_plans: addon_plan = self._get_and_check_addon_plan( plan_addon_names_by_owner.get(reseller_login, dict()), addon_plan_name, reseller_login, subscription_report ) if addon_plan is not None: subscription.plan_addon_names.append(addon_plan.name) else: # there are critical errors, so do not consider # that subscription anymore in further checks and conversions del self.raw_target_subscriptions[subscription.name] else: # custom subscription - subscription that is not assigned to any plan # so leave plan_name set to None pass else: logger.debug( messages.DEBUG_SUBSCRIPTION_NOT_LISTED_IN_MIGRATION_LIST, subscription.name ) del self.raw_target_subscriptions[subscription.name] # 2.2 subscriptions <- client, according to migration list for subscription in self.raw_target_subscriptions.values(): if subscription.name in subscriptions_mapping: subscription_mapping = subscriptions_mapping[subscription.name] subscription_owner = subscription_mapping.owner if subscription_owner in self.raw_target_clients: # add subscription to model. guard against multiple addition of # the same subscription (if it exists on two Plesk servers) client_subscriptions = self.raw_target_clients[subscription_owner].subscriptions client_subscription_names = [subs.name for subs in client_subscriptions] if subscription.name not in client_subscription_names: self.raw_target_clients[subscription_owner].subscriptions.append(subscription) else: plain_report.get_subscription_report(subscription.name).add_issue( "customer_does_not_exist", Issue.SEVERITY_ERROR, messages.CUSTOMER_DOES_NOT_EXIST_ISSUE % subscription_owner, messages.CUSTOMER_DOES_NOT_EXIST_ISSUE_SOLUTION ) del self.raw_target_subscriptions[subscription.name] else: logger.debug( messages.DEBUG_SUBSCRIPTION_NOT_LISTED_IN_MIGRATION_LIST, subscription.name ) del self.raw_target_subscriptions[subscription.name] # 2.3 client <- admin, reseller, according to migration list def add_client_to_model(client, reseller_login): if reseller_login is None: if client.login not in self.target_clients: self.target_clients[client.login] = client elif reseller_login in self.target_resellers: reseller_clients_by_login = group_by_id( self.target_resellers[reseller_login].clients, lambda c: c.login ) if client.login not in reseller_clients_by_login: self.target_resellers[reseller_login].clients.append(client) else: plain_report.get_customer_report(client.login).add_issue( 'reseller_does_not_exist', Issue.SEVERITY_ERROR, messages.CLIENT_MAPPED_TO_RESELLER_THAT_DOES_NOT_EXIST_ISSUE % reseller_login, messages.CLIENT_MAPPED_TO_RESELLER_THAT_DOES_NOT_EXIST_SOLUTION ) backup_reseller_logins = {reseller.login for reseller in backup.iter_resellers()} target_reseller_logins = set(self.existing_objects.resellers.keys()) all_reseller_logins = backup_reseller_logins | target_reseller_logins for client in self.raw_target_clients.values(): if client.login in customers_mapping or client.login is None: reseller_login = customers_mapping.get(client.login) add_client_to_model(client, reseller_login) elif client.login in all_reseller_logins: add_client_to_model(client, client.login) else: logger.debug(messages.DEBUG_CLIENT_IS_NOT_LISTED_IN_MIGRATION_LIST % client.login) del self.raw_target_clients[client.login] def _add_client( self, client, reseller, plesk_id, plain_report, password_holder, customer_mapping, admin_descriptions ): client_report = plain_report.get_customer_report(client.login) if client.login in self.existing_objects.customers: existing_client = self.existing_objects.customers[client.login] new_client = self._create_target_client_from_backup( client, reseller, None, Report('', ''), password_holder, admin_descriptions ) # just for easy comparison check_client_contacts_matching_source_target(new_client, existing_client, client_report) self._check_client_owner(client_report, customer_mapping, existing_client) target_model_client = self.entity_converter.create_client_stub_from_existing_client(existing_client) self.raw_target_clients[client.login] = target_model_client elif client.login in self.raw_target_clients: existing_client = self.raw_target_clients[client.login] new_client = self._create_target_client_from_backup( client, reseller, None, Report('', ''), password_holder, admin_descriptions ) # just for easy comparison check_client_contacts_matching_source_panels(new_client, existing_client, client_report) target_model_client = existing_client else: target_model_client = self._create_target_client_from_backup( client, reseller, plesk_id, client_report, password_holder, admin_descriptions ) self.raw_target_clients[client.login] = target_model_client return target_model_client def _check_client_owner(self, client_report, customer_mapping, existing_client): admin_id = Registry.get_instance().get_context().hosting_repository.panel.get_administrator_id(None) # check existing client owner if existing_client.owner_id == admin_id: if customer_mapping is not None: client_report.add_issue( "owned_by_another_admin", Issue.SEVERITY_ERROR, messages.CUSTOMER_BELONGS_TO_DIFFERENT_OWNER_ADMIN_RESELLER_ISSUE % (customer_mapping,), messages.CUSTOMER_BELONGS_TO_DIFFERENT_OWNER_ADMIN_RESELLER_ISSUE_SOLUTION) else: # Customer is already owned by some reseller target_reseller = find_first( self.existing_objects.resellers.values(), lambda r: str(r.reseller_id) == str(existing_client.owner_id) ) if target_reseller is None or target_reseller.username != customer_mapping: client_report.add_issue( "owned_by_another_reseller", Issue.SEVERITY_ERROR, messages.CUSTOMER_BELONGS_TO_DIFFERENT_OWNER_RESELLER_ISSUE % ( 'admin' if customer_mapping is None else messages.RESELLER_TITLE_LOWERCASE % ( customer_mapping, ) ), messages.CUSTOMER_BELONGS_TO_DIFFERENT_OWNER_RESELLER_SOLUTION ) def _create_target_client_from_backup( self, client, reseller, source, client_report, password_holder, admin_descriptions ): return self.entity_converter.create_client_from_plesk_backup_client( client, reseller, source, client_report, password_holder, encrypted_passwords_supported=self._encrypted_passwords_supported, admin_descriptions=admin_descriptions ) @staticmethod def _check_plan_exists(reseller_plans, plan_name, reseller_login, subscription_report): """Check is plan with given name exists in list of plans, retrieved from target panel with the same vendor, and add an error into report if not :type reseller_plans: dict :type plan_name: str :type reseller_login: str :type subscription_report: parallels.core.reports.model.report.Report """ if plan_name not in reseller_plans: if reseller_login is not None: owner_title = messages.RESELLER_TITLE_LOWERCASE % reseller_login else: owner_title = "admin" subscription_report.add_issue( "plan_does_not_exist", Issue.SEVERITY_ERROR, messages.PLAN_DOES_NOT_EXIST_ISSUE.format( owner=owner_title, plan_name=plan_name ), messages.PLAN_DOES_NOT_EXIST_SOLUTION ) def _get_and_check_addon_plan(self, addon_plans, addon_plan_name, reseller_login, subscription_report): """Get addon plan object for specified owner by name, check addon plan existence If addon exists - just return it. If addon does not exist - add issue to the report and return None. :type addon_plans: dict :type addon_plan_name: str | unicode :type reseller_login: str | unicode :type subscription_report: parallels.core.reports.model.report.Report :rtype: parallels.core.hosting_repository.service_plan_addon.ServicePlanAddonEntity """ addon_plan = addon_plans.get(addon_plan_name) if addon_plan is None: if reseller_login is not None: owner_title = messages.RESELLER_TITLE_LOWERCASE % reseller_login else: owner_title = "admin" subscription_report.add_issue( "addon_plan_does_not_exist", Issue.SEVERITY_ERROR, messages.ADDON_PLAN_DOES_NOT_EXIST_ISSUE.format(owner=owner_title, plan_name=addon_plan_name), messages.PLAN_DOES_NOT_EXIST_SOLUTION ) return addon_plan @classmethod def _get_plans_by_owners(cls, hosting_plans): """Returns the following structure: plans[owner][plan name] = plan where owner is reseller login or None in case of admin :type hosting_plans: dict[str|None, list[parallels.core.target_data_model.Plan]] :rtype: dict[str | None, dict[str, parallels.core.target_data_model.Plan]] """ plans_by_owners = {} for owner_username, owner_plans in hosting_plans.items(): plans_by_owners[owner_username] = {p.name: p for p in owner_plans if not p.is_addon} return plans_by_owners @classmethod def _get_plan_addons_by_owners(cls, hosting_plans): """Returns the following structure: plans[owner][plan_addon_name] = plan where owner is reseller login or None in case of admin :type hosting_plans: dict[str|None, list[parallels.core.target_data_model.Plan]] :rtype: dict[str | None, dict[str, parallels.core.target_data_model.Plan]] """ plans_by_owners = {} for owner_username, owner_plans in hosting_plans.items(): plans_by_owners[owner_username] = {p.name: p for p in owner_plans if p.is_addon} return plans_by_owners def _add_subscription(self, subscription, reseller, plesk_id, is_windows, plain_report, backup): issues = [] webspaces_by_name = group_by_id(self.existing_objects.subscriptions, lambda s: s.name_canonical) if subscription.name_canonical in webspaces_by_name: model_subscription = self.entity_converter.create_subscription_stub_from_existing_subscription( subscription, plesk_id, is_windows ) else: model_subscription = self.entity_converter.create_subscription_from_plesk_backup_subscription( subscription, reseller, plesk_id, is_windows, backup.admin_descriptions ) issues += check_subscription_conflicts( subscription.name, source_webspaces=self.raw_target_subscriptions, source_sites=self.target_site_names, target_sites=self.existing_site_names, ) issues += check_subscription_conflicts( subscription.name[4:] if subscription.name.startswith('www.') else 'www.' + subscription.name, source_webspaces=self.raw_target_subscriptions, source_sites=self.target_site_names, target_sites=self.existing_site_names, ) issues += check_domain_conflicts( backup, subscription.name, target_webspaces=self.existing_objects.subscriptions, target_sites=self.existing_site_names, source_webspaces=self.raw_target_subscriptions, source_sites=self.target_site_names, ) for issue in issues: plain_report.get_subscription_report(subscription.name).add_issue(*issue) critical_issues = [issue for issue in issues if issue[1] == Issue.SEVERITY_ERROR] if len(critical_issues) == 0: self.raw_target_subscriptions[subscription.name] = model_subscription domain_objects = index_plesk_backup_domain_objects(backup, subscription.name) for name, kind in domain_objects: self.target_site_names[name] = (subscription.name, kind)