from collections import defaultdict
from contextlib import contextmanager
from xml.etree import ElementTree
import ntpath
import os

from parallels.core import MigrationError
from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.reports.model.issue import Issue
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.utils.database_utils import is_local_database_host
from parallels.core.utils.common import cached, group_value_by, exists, safe_format
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.capability_dump.model.base.admin import Admin
from parallels.plesk.source.plesk.capability_dump.model.base.client import Client
from parallels.plesk.source.plesk.capability_dump.model.base.database_server import DatabaseServer
from parallels.plesk.source.plesk.capability_dump.model.base.domain import Domain
from parallels.plesk.source.plesk.capability_dump.model.base.ip_address import IpAddress
from parallels.plesk.source.plesk.capability_dump.model.base.reseller import Reseller
from parallels.plesk.source.plesk.capability_dump.model.plesk_base import PleskBaseCapabilityDumpModel
from parallels.plesk.source.plesk.capability_dump.utils import capability_data_dump
import parallels.plesk.source.plesk.capability_dump.model.plesk_entity as plesk_entity

logger = get_logger(__name__)


class PleskCapabilityDumpModel(PleskBaseCapabilityDumpModel):
    """
    Use the method _entity_data_dump() and the decorator capability_data_dump() to catch unhandled exceptions
    """

    @cached
    def get_admin(self):
        """
        :rtype: parallels.plesk.source.plesk.capability_dump.model.base.admin.Admin
        :raises: parallels.core.MigrationError
        """
        for plesk_client in self._get_plesk_clients(client_type='admin'):
            if plesk_client.client_type == 'admin':
                return Admin(
                    admin_id=plesk_client.client_id, login=plesk_client.login
                )
        raise MigrationError(messages.UNABLE_TO_FIND_ADMIN_USER)

    def get_resellers(self):
        """
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.base.reseller.Reseller]
        """
        resellers = []
        for plesk_client in self._get_plesk_clients(client_type='reseller'):
            with self._entity_data_dump(messages.ENTITY_DUMP_RESELLER, plesk_client.login):
                if not self._selection.is_reseller_selected(plesk_client.login):
                    continue
                resellers.append(Reseller(
                    reseller_id=plesk_client.client_id, login=plesk_client.login
                ))
        return resellers

    def get_clients(self, owner_id):
        """
        :type owner_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.base.client.Client]
        """
        clients = []
        for plesk_client in self._get_plesk_clients(owner_id=owner_id, client_type='client'):
            with self._entity_data_dump(messages.ENTITY_DUMP_CUSTOMER, plesk_client.login):
                if not self._selection.is_client_selected(plesk_client.login):
                    continue
                clients.append(Client(
                    client_id=plesk_client.client_id, login=plesk_client.login, owner_id=plesk_client.owner_id
                ))
        return clients

    def get_domains(self, owner_id):
        """
        :type owner_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.base.domain.Domain]
        """
        domains = []
        for plesk_domain in self._get_plesk_domains(owner_id=owner_id, webspace_id='0'):
            with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, plesk_domain.name):
                if not self._selection.is_domain_selected(plesk_domain.name):
                    continue
                plesk_client = self._get_all_plesk_clients().get(plesk_domain.vendor_id)
                if not plesk_client:
                    continue
                domains.append(Domain(
                    domain_id=plesk_domain.domain_id, name=plesk_domain.name, owner_id=plesk_domain.owner_id,
                    vendor_login=plesk_client.login
                ))
        return domains

    @capability_data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, [])
    def get_server_components(self):
        """Returns list like
            [('package.asp', '8.5.9600.16384'), ('package.asp.net.4', '4.5.1') ...]

        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = self._get_server_installed_components()
        components += self._get_server_php_handler_components()
        components += self._get_server_installed_modules()
        return components

    @capability_data_dump(messages.DATA_DUMP_SERVER_RESOURCES_USAGE, [])
    def get_server_resource_usage(self):
        """Returns list like
            [('resource.domains', '10'), ('resource.accounts', '6'), ('resource.aliases', '0') ...]

        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        resource_usage = self._get_server_account_usage()
        return resource_usage

    @capability_data_dump(messages.DATA_DUMP_SERVER_SETTINGS, [])
    def get_server_settings(self):
        """Returns list like
            [('ftpOverSsl', 'enabled'), ('ftps_10.52.71.29', 'false') ...]

        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        settings = self._get_server_ftp_settings()
        if self._is_dns_enabled():
            settings += self._get_server_dns_settings()
        settings += self._get_server_mail_settings()
        settings += self._get_server_mod_security_settings()
        return settings

    def get_database_servers(self):
        """
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.base.database_server.DatabaseServer]
        """
        database_servers = []
        local_ips = [ip.ip_address for ip in self._get_all_plesk_ip_addresses().values()]
        for plesk_database_server in self._get_all_plesk_database_servers().values():
            database_server_name = '%s://%s:%s' % (plesk_database_server.server_type, plesk_database_server.host, plesk_database_server.port)
            with self._entity_data_dump(messages.ENTITY_DUMP_DATABASE_SERVER, database_server_name):
                database_servers.append(DatabaseServer(
                    server_type=plesk_database_server.server_type,
                    host=plesk_database_server.host,
                    port=plesk_database_server.port,
                    local='true' if is_local_database_host(plesk_database_server.host, plesk_database_server.server_type, local_ips) else 'false',
                    version=plesk_database_server.version,
                ))
        return database_servers

    @capability_data_dump(messages.DATA_DUMP_APACHE_MODULES, [])
    def get_apache_modules(self):
        """Returns list like
            [('auth_basic', 'on'), ('php5', 'off') ...]

        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        modules = []
        if not self._dump_agent.source_server().is_windows():
            with self._dump_agent.source_server().runner() as runner:
                assert isinstance(runner, BaseRunner)
                plesk_dir = self._dump_agent.source_server().plesk_dir
                stdout = runner.execute_command(
                    "{httpd_modules_ctl} --status --all-modules",
                    dict(httpd_modules_ctl=os.path.join(plesk_dir, 'admin/bin/httpd_modules_ctl'))
                ).stdout
                for line in [l.strip() for l in stdout.splitlines() if l.strip()]:
                    name, value = line.split(' ')
                    modules.append((name.strip(), value.strip()))
        return modules

    def get_domain_ips(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.base.ip_address.IpAddress]
        """
        plesk_domain = self._get_all_plesk_domains().get(domain_id)
        plesk_client = self._get_all_plesk_clients().get(plesk_domain.owner_id)
        plesk_web_service = self._get_plesk_domain_service(domain_id, 'web')
        domain_ips = []
        if plesk_domain and plesk_client and plesk_web_service:
            with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, plesk_domain.name, messages.DATA_DUMP_IP_ADDRESSES):
                plesk_ip_pool = self._get_all_plesk_ip_pool_items().get(plesk_client.ip_pool_id, [])
                for plesk_ip_pool_item in plesk_ip_pool:
                    if plesk_ip_pool_item.ip_address_id not in plesk_web_service.ip_address_ids:
                        continue
                    plesk_ip_address = self._get_all_plesk_ip_addresses().get(plesk_ip_pool_item.ip_address_id)
                    if not plesk_ip_address:
                        continue
                    domain_ips.append(IpAddress(
                        ip_address_id=plesk_ip_address.ip_address_id, ip_address=plesk_ip_address.ip_address,
                        ip_address_type=plesk_ip_pool_item.ip_address_type
                    ))
        return domain_ips

    def get_domain_components(self, domain_id):
        """Returns list like
            [('webmail', 'horde'), ('asp_dot_net', 'true'), ('php_handler_type', 'fastcgi') ...]

        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_COMPONENTS):
            components += self._get_domain_hosting_components(domain_id)
            components += self._get_domain_webmail_components(domain_id)
            if self._is_dns_enabled():
                components += self._get_domain_dns_components(domain_id)
            components += self._get_domain_mail_components(domain_id)
            components += self._get_domain_maillist_components(domain_id)
            components += self._get_domain_database_components(domain_id)
            components += self._get_domain_outgoing_messages_components(domain_id)
        return components

    def get_domain_resource_usage(self, domain_id):
        """Returns list like
            [('diskusage.vhost', '189478'), ('diskusage.db', '14589'), ('resource.mailaccounts', '3') ...]

        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        resource_usage = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_RESOURCES_USAGE):
            resource_usage += self._get_domain_disk_usage(domain_id)
            resource_usage += self._get_domain_account_usage(domain_id)
        return resource_usage

    def get_domain_settings(self, domain_id):
        """Returns list like
            [('sysuser', 'user29'), ('stat_ttl', '3'), ('iis_app_pool', 'true') ...]

        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        settings = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_SETTINGS):
            settings += self._get_domain_sys_users(domain_id)
            settings += self._get_domain_parameters(domain_id)
            settings += self._get_domain_wordpress_settings(domain_id)
            if self._is_dns_enabled():
                settings += self._get_domain_dns_settings(domain_id)
            settings += self._get_domain_mod_security_settings(domain_id)
            if self._dump_agent.source_server().is_windows():
                settings += self._get_domain_iis_app_pool_settings(domain_id)
            else:
                settings += self._get_domain_postgresql_settings(domain_id)
        return settings

    @cached
    @capability_data_dump(messages.DATA_DUMP_SUBSCRIPTIONS, {})
    def _get_all_plesk_subscriptions(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.subscription.Subscription]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `object_id`, `object_type`
            FROM `Subscriptions`
        """)
        return {
            row['id']: plesk_entity.Subscription(
                subscription_id=row['id'], object_id=row['object_id'], object_type=row['object_type'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_SUBSCRIPTIONS_PROPERTIES, {})
    def _get_all_plesk_subscriptions_properties(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `subscription_id`, `name`, `value`
            FROM `SubscriptionProperties`
        """)
        properties = defaultdict(dict)
        for row in rows:
            properties[row['subscription_id']][row['name']] = row['value']
        return properties

    @cached
    @capability_data_dump(messages.DATA_DUMP_CLIENTS, {})
    def _get_all_plesk_clients(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.client.Client]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `parent_id`, `login`, `type`, `pool_id`
            FROM `clients`
        """)
        return {
            row['id']: plesk_entity.Client(
                client_id=row['id'], owner_id=row['parent_id'], login=row['login'], client_type=row['type'],
                ip_pool_id=row['pool_id'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DOMAINS, {})
    def _get_all_plesk_domains(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.domain.Domain]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `webspace_id`, `cl_id`, `vendor_id`, `dns_zone_id`, `displayName`, `name`, `htype`
            FROM `domains`
        """)
        return {
            row['id']: plesk_entity.Domain(
                domain_id=row['id'], webspace_id=row['webspace_id'], owner_id=row['cl_id'],
                vendor_id=row['vendor_id'], dns_zone_id=row['dns_zone_id'], name=row['displayName'],
                ascii_name=row['name'], hosting_type=row['htype'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def _get_all_plesk_hosting(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.hosting.Hosting]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `dom_id`, `asp`, `asp_dot_net`, `certificate_id`, `managed_runtime_version`, `perl`, `php`,
                `php_handler_id`, `python`, `ssi`, `ssl`, `sys_user_id`, `webstat`, `webdeploy`
            FROM `hosting`
        """)
        return {
            row['dom_id']: plesk_entity.Hosting(
                domain_id=row['dom_id'], asp=row['asp'], asp_dot_net=row['asp_dot_net'],
                certificate_id=row['certificate_id'], managed_runtime_version=row['managed_runtime_version'],
                perl=row['perl'], php=row['php'], php_handler_id=row['php_handler_id'], python=row['python'],
                ssi=row['ssi'], ssl=row['ssl'], sys_user_id=row['sys_user_id'], webstat=row['webstat'],
                webdeploy=row['webdeploy'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DOMAINS_SERVICES_SETTINGS, {})
    def _get_all_plesk_domains_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `dom_id`, `param`, `val`
            FROM `dom_param`
        """)
        parameters = defaultdict(dict)
        for row in rows:
            parameters[row['dom_id']][row['param']] = row['val']
        return parameters

    @cached
    @capability_data_dump(messages.DATA_DUMP_DOMAIN_ALIASES, {})
    def _get_all_plesk_domain_aliases(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.domain_alias.DomainAlias]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dom_id`
            FROM `domain_aliases`
        """)
        return {
            row['id']: plesk_entity.DomainAlias(
                domain_alias_id=row['id'], domain_id=row['dom_id'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DNS_ZONES, {})
    def _get_all_plesk_dns_zones(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.dns_zone.DnsZone]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`
            FROM `dns_zone`
        """)
        return {
            row['id']: plesk_entity.DnsZone(
                dns_zone_id=row['id']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DNS_RECORDS, {})
    def _get_all_plesk_dns_records(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.dns_record.DnsRecord]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dns_zone_id`, `type`, `opt`
            FROM `dns_recs`
        """)
        return {
            row['id']: plesk_entity.DnsRecord(
                dns_record_id=row['id'], dns_zone_id=row['dns_zone_id'], dns_record_type=row['type'], opt=row['opt'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DNS_TEMPLATES_RECORDS, {})
    def _get_all_plesk_dns_template_records(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.dns_template_record.DnsTemplateRecord]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `type`, `opt`
            FROM `dns_recs_t`
        """)
        return {
            row['id']: plesk_entity.DnsTemplateRecord(
                dns_template_record_id=row['id'], dns_zone_type=row['type'], opt=row['opt'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_SYSTEM_USERS, {})
    def _get_all_plesk_sys_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.sys_user.SysUser]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `login`, `shell`, `quota`
            FROM `sys_users`
        """)
        return {
            row['id']: plesk_entity.SysUser(
                sys_user_id=row['id'], login=row['login'], shell=row['shell'], quota=row['quota'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_WEB_USERS, {})
    def _get_all_plesk_web_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.web_user.WebUser]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dom_id`, `sys_user_id`
            FROM `web_users`
        """)
        return {
            row['id']: plesk_entity.WebUser(
                web_user_id=row['id'], domain_id=row['dom_id'], sys_user_id=row['sys_user_id']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_FTP_USERS, {})
    def _get_all_plesk_ftp_users(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.ftp_user.FtpUser]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dom_id`, `sys_user_id`
            FROM `ftp_users`
        """)
        return {
            row['id']: plesk_entity.FtpUser(
                ftp_user_id=row['id'], domain_id=row['dom_id'], sys_user_id=row['sys_user_id']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_MAILBOXES, {})
    def _get_all_plesk_mail_names(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.mail_name.MailName]
        """
        if self._dump_agent.source_server().is_windows():
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `mail_name`
                FROM `mail`
            """)
            return {
                row['id']: plesk_entity.MailName(
                    mail_name_id=row['id'], domain_id=row['dom_id'], name=row['mail_name'],
                    spamfilter=None, virusfilter=None,
                )
                for row in rows
            }
        else:
            rows = self._dump_agent.execute_sql("""
                SELECT `id`, `dom_id`, `mail_name`, `spamfilter`, `virusfilter`
                FROM `mail`
            """)
            return {
                row['id']: plesk_entity.MailName(
                    mail_name_id=row['id'], domain_id=row['dom_id'], name=row['mail_name'],
                    spamfilter=row['spamfilter'], virusfilter=row['virusfilter'],
                )
                for row in rows
            }

    @cached
    @capability_data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def _get_all_plesk_mail_names_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `mn_id`, `param`, `val`
            FROM `mn_param`
        """)
        parameters = defaultdict(dict)
        for row in rows:
            parameters[row['mn_id']][row['param']] = row['val']
        return parameters

    @cached
    @capability_data_dump(messages.DATA_DUMP_MAILLISTS, {})
    def _get_all_plesk_mail_lists(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.mail_list.MailList]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dom_id`, `name`
            FROM `MailLists`
        """)
        return {
            row['id']: plesk_entity.MailList(
                mail_list_id=row['id'], domain_id=row['dom_id'], name=row['name'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_SPAM_FILTERING_SETTINGS, {})
    def _get_all_plesk_sa_mail_names(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.sa_mail_name.SpamAssassinMailName]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `mailname`, `flt_enabled`
            FROM `sa_conf`
        """)
        return {
            row['mailname']: plesk_entity.SpamAssassinMailName(
                mail_name_id=row['id'], name=row['mailname'], flt_enabled=row['flt_enabled'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DOMAINS_SERVICES_SETTINGS, {})
    def _get_all_plesk_domain_services(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.base.domain_service.DomainService]]
        """
        ip_addresses = group_value_by(
            self._dump_agent.execute_sql("SELECT `ipCollectionId`, `ipAddressId` FROM `IpAddressesCollections`"),
            lambda item: item['ipCollectionId'],
            lambda item: item['ipAddressId']
        )
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `dom_id`, `type`, `ipCollectionId`, `parameters_id`
            FROM `DomainServices`
        """)
        services = defaultdict(dict)
        for row in rows:
            services[row['dom_id']][row['type']] = plesk_entity.DomainService(
                domain_service_id=row['id'], domain_id=row['dom_id'], service_type=row['type'],
                ip_address_ids=ip_addresses.get(row['ipCollectionId'], []), parameters_id=row['parameters_id'],
            )
        return services

    @cached
    @capability_data_dump(messages.DATA_DUMP_IP_ADDRESSES, {})
    def _get_all_plesk_ip_pool_items(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.ip_pool_item.IpPoolItem]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `ip_address_id`, `type`
            FROM `ip_pool`
        """)
        ip_pool_items = defaultdict(list)
        for row in rows:
            ip_pool_items[row['id']].append(plesk_entity.IpPoolItem(
                ip_pool_id=row['id'], ip_address_id=row['ip_address_id'],
                ip_address_type=row['type'],
            ))
        return ip_pool_items

    @cached
    @capability_data_dump(messages.DATA_DUMP_IP_ADDRESSES, {})
    def _get_all_plesk_ip_addresses(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.ip_address.IpAddress]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `ip_address`, `serviceNodeId`, `ftps`
            FROM `IP_Addresses`
        """)
        return {
            row['id']: plesk_entity.IpAddress(
                ip_address_id=row['id'], ip_address=row['ip_address'], service_node_id=row['serviceNodeId'],
                ftps=row['ftps'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DATABASE_SERVERS, {})
    def _get_all_plesk_database_servers(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.database_server.DatabaseServer]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `type`, `host`, `port`, `server_version`
            FROM `DatabaseServers`
            WHERE `last_error` <> 'credentials_not_set'
        """)
        return {
            row['id']: plesk_entity.DatabaseServer(
                database_server_id=row['id'],
                server_type=row['type'],
                host=row['host'],
                port=row['port'],
                version=row['server_version'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_DATABASES, {})
    def _get_all_plesk_databases(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.database.Database]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `db_server_id`, `dom_id`
            FROM `data_bases`
        """)
        return {
            row['id']: plesk_entity.Database(
                database_id=row['id'],
                database_server_id=row['db_server_id'],
                domain_id=row['dom_id'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_WORDPRESS_INSTANCES, {})
    def _get_all_plesk_wordpress_instances(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.wordpress_instance.WordpressInstance]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `subscriptionId`
            FROM `WordpressInstances`
        """)
        return {
            row['id']: plesk_entity.WordpressInstance(
                wordpress_instance_id=row['id'],
                subscription_id=row['subscriptionId'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_APPLICATIONS, {})
    def _get_all_plesk_webapps(self):
        """
        :rtype: dict[str|unicode, list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.webapp.Webapp]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `domain_service_id`
            FROM `WebApps`
        """)
        webapps = defaultdict(list)
        for row in rows:
            webapps[row['domain_service_id']].append(plesk_entity.Webapp(
                webapp_id=row['id'], domain_service_id=row['domain_service_id'],
            ))
        return webapps

    @cached
    @capability_data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def _get_all_plesk_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `parameter`, `value`
            FROM `Parameters`
        """)
        parameters = defaultdict(dict)
        for row in rows:
            parameters[row['id']][row['parameter']] = row['value']
        return parameters

    @cached
    @capability_data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def _get_all_plesk_web_server_settings(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.web_server_settings.WebServerSettings]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`
            FROM `WebServerSettings`
        """)
        return {
            row['id']: plesk_entity.WebServerSettings(
                web_server_settings_id=row['id']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def _get_all_plesk_web_server_settings_parameters(self):
        """
        :rtype: dict[str|unicode, dict[str|unicode, str|unicode]]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `webServerSettingsId`, `name`, `value`
            FROM `WebServerSettingsParameters`
        """)
        parameters = defaultdict(dict)
        for row in rows:
            parameters[row['webServerSettingsId']][row['name']] = row['value']
        return parameters

    @cached
    @capability_data_dump(messages.DATA_DUMP_DISK_USAGE, {})
    def _get_all_plesk_disk_usage(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.disk_usage.DiskUsage]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `dom_id`, `httpdocs`, `httpsdocs`, `subdomains`, `web_users`, `anonftp`, `mysql_dbases`,
                `mssql_dbases`, `mailboxes`, `webapps`, `maillists`
            FROM `disk_usage`
        """)
        return {
            row['dom_id']: plesk_entity.DiskUsage(
                domain_id=row['dom_id'], httpdocs=row['httpdocs'], httpsdocs=row['httpsdocs'],
                subdomains=row['subdomains'], web_users=row['web_users'], anonftp=row['anonftp'],
                mysql_dbases=row['mysql_dbases'], mssql_dbases=row['mssql_dbases'], mailboxes=row['mailboxes'],
                webapps=row['webapps'], maillists=row['maillists'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_IIS_APPLICATIONS_POOLS_SETTINGS, {})
    def _get_all_plesk_iis_app_pools(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.iis_app_pool.IisAppPool]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `ownerId`, `ownerType`
            FROM `IisAppPools`
        """)
        return {
            row['id']: plesk_entity.IisAppPool(
                iis_app_pool_id=row['id'], owner_id=row['ownerId'], owner_type=row['ownerType']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_IIS_APPLICATIONS_POOLS_SETTINGS, {})
    def _get_all_plesk_iis_app_pool_domains(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.iis_app_pool_domain.IisAppPoolDomain]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `domainId`, `poolId`
            FROM `IisAppPoolDomains`
        """)
        return {
            row['domainId']: plesk_entity.IisAppPoolDomain(
                domain_id=row['domainId'], iis_app_pool_id=row['poolId']
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_EXTENSIONS, {})
    def _get_all_plesk_modules(self):
        """
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.module.Module]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `id`, `name`, `version`, `release`
            FROM `Modules`
        """)
        return {
            row['id']: plesk_entity.Module(
                module_id=row['id'], name=row['name'], version=row['version'], release=row['release'],
            )
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, ElementTree.Element('packages'))
    def _get_all_packages(self):
        """
        :rtype: xml.etree.ElementTree.Element
        """
        with self._dump_agent.source_server().runner() as runner:
            assert isinstance(runner, BaseRunner)
            packages_xml = runner.execute_command(
                '{defpackagemng} --get',
                dict(
                    defpackagemng=ntpath.join(self._dump_agent.source_server().plesk_dir, r'admin\bin\defpackagemng')
                ),
            ).stdout
            return ElementTree.fromstring(packages_xml)

    @cached
    def _get_plesk_subscription(self, object_id, object_type):
        """
        :type object_id: str|unicode
        :type object_type: str|unicode
        :rtype: parallels.plesk.source.plesk.capability_dump.model.plesk_entity.subscription.Subscription | None
        """
        for plesk_subscription in self._get_all_plesk_subscriptions().values():
            if plesk_subscription.object_id == object_id and plesk_subscription.object_type == object_type:
                return plesk_subscription
        return None

    def _get_plesk_subscription_property(self, subscription_id, name):
        """
        :rtype: str|unicode|None
        """
        properties = self._get_all_plesk_subscriptions_properties().get(subscription_id)
        if properties:
            return properties.get(name)
        return None

    @cached
    def _get_plesk_clients(self, owner_id=None, client_type=None):
        """
        :type owner_id: str|unicode
        :type client_type: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.client.Client]
        """
        return [
            plesk_client for plesk_client in self._get_all_plesk_clients().values()
            if (not owner_id or plesk_client.owner_id == owner_id) and
            (not client_type or plesk_client.client_type == client_type)
        ]

    @cached
    def _get_plesk_domains(self, domain_id=None, owner_id=None, webspace_id=None, hosting_type=None):
        """
        :type domain_id: str|unicode|None
        :type owner_id: str|unicode|None
        :type webspace_id: str|unicode|None
        :type hosting_type: str|unicode|None
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.domain.Domain]
        """
        if domain_id is None:
            plesk_domains = self._get_all_plesk_domains().values()
        else:
            plesk_domain = self._get_all_plesk_domains().get(domain_id)
            plesk_domains = [plesk_domain] if plesk_domain else []
        return [
            dom for dom in plesk_domains
            if (not owner_id or dom.owner_id == owner_id) and
            (not webspace_id or dom.webspace_id == webspace_id) and
            (not hosting_type or dom.hosting_type == hosting_type)
        ]

    def _get_plesk_domain_parameter(self, domain_id, name, default=None):
        """
        :type domain_id: str|unicode
        :type name: str|unicode
        :type default: str|unicode|None
        :rtype: str|unicode|None
        """
        parameters = self._get_all_plesk_domains_parameters().get(domain_id)
        if parameters:
            return parameters.get(name, default)
        return default

    @cached
    def _get_plesk_domain_aliases(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.domain_alias.DomainAlias]
        """
        return [
            plesk_domain_alias for plesk_domain_alias in self._get_all_plesk_domain_aliases().values()
            if (not domain_id or plesk_domain_alias.domain_id == domain_id)
        ]

    @cached
    def _get_plesk_dns_records(self, dns_zone_id=None):
        """
        :type dns_zone_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.dns_record.DnsRecord]
        """
        return [
            plesk_dns_record for plesk_dns_record in self._get_all_plesk_dns_records().values()
            if (not dns_zone_id or plesk_dns_record.dns_zone_id == dns_zone_id)
        ]

    @cached
    def _get_plesk_webspace_domains(self, webspace_id, hosting_type=None):
        """
        :type webspace_id: str|unicode
        :type hosting_type: str|unicode|None
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.domain.Domain]
        """
        plesk_domains = self._get_plesk_domains(domain_id=webspace_id, hosting_type=hosting_type)
        plesk_domains.extend(self._get_plesk_domains(webspace_id=webspace_id, hosting_type=hosting_type))
        return plesk_domains

    @cached
    def _get_plesk_webspace_hosting(self, webspace_id):
        """
        :type webspace_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.hosting.Hosting]
        """
        hosting = []
        for plesk_domain in self._get_plesk_webspace_domains(webspace_id, 'vrt_hst'):
            plesk_hosting = self._get_all_plesk_hosting().get(plesk_domain.domain_id)
            if plesk_hosting:
                hosting.append(plesk_hosting)
        return hosting

    @cached
    def _get_plesk_web_users(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.web_user.WebUser]
        """
        return [
            plesk_web_user for plesk_web_user in self._get_all_plesk_web_users().values()
            if (not domain_id or plesk_web_user.domain_id == domain_id)
        ]

    @cached
    def _get_plesk_ftp_users(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.web_user.FtpUser]
        """
        return [
            plesk_ftp_user for plesk_ftp_user in self._get_all_plesk_ftp_users().values()
            if (not domain_id or plesk_ftp_user.domain_id == domain_id)
        ]

    @cached
    def _get_plesk_mail_names(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.mail_name.MailName]
        """
        return [
            plesk_mail_name for plesk_mail_name in self._get_all_plesk_mail_names().values()
            if (not domain_id or plesk_mail_name.domain_id == domain_id)
        ]

    def _get_plesk_mail_name_parameter(self, mail_name_id, name):
        """
        :rtype: str|unicode|None
        """
        parameters = self._get_all_plesk_mail_names_parameters().get(mail_name_id)
        if parameters:
            return parameters.get(name)
        return None

    @cached
    def _get_plesk_mail_lists(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.mail_list.MailList]
        """
        return [
            plesk_mail_list for plesk_mail_list in self._get_all_plesk_mail_lists().values()
            if (not domain_id or plesk_mail_list.domain_id == domain_id)
        ]

    def _get_plesk_domain_service(self, domain_id, domain_service_type):
        """
        :type domain_id: str|unicode
        :type domain_service_type: str|unicode
        :rtype: parallels.plesk.source.plesk.capability_dump.model.base.domain_service.DomainService | None
        """
        domain_services = self._get_all_plesk_domain_services().get(domain_id)
        if domain_services:
            return domain_services.get(domain_service_type)
        return None

    @cached
    def _get_plesk_databases(self, domain_id=None):
        """
        :type domain_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.database.Database]
        """
        return [
            plesk_database for plesk_database in self._get_all_plesk_databases().values()
            if (not domain_id or plesk_database.domain_id == domain_id)
        ]

    @cached
    def _get_plesk_wordpress_instances(self, subscription_id=None):
        """
        :type subscription_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.wordpress_instance.WordpressInstance]
        """
        return [
            plesk_wordpress_instance for plesk_wordpress_instance in self._get_all_plesk_wordpress_instances().values()
            if (not subscription_id or plesk_wordpress_instance.subscription_id == subscription_id)
        ]

    def _get_plesk_parameter(self, parameters_id, name):
        """
        :rtype: str|unicode|None
        """
        parameters = self._get_all_plesk_parameters().get(parameters_id)
        if parameters:
            return parameters.get(name)
        return None

    def _get_plesk_web_server_settings_parameter(self, web_server_settings_id, name):
        """
        :rtype: str|unicode|None
        """
        parameters = self._get_all_plesk_web_server_settings_parameters().get(web_server_settings_id)
        if parameters:
            return parameters.get(name)
        return None

    @cached
    @capability_data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, {})
    def _get_plesk_service_node_environment(self, service_node_id, section_name):
        """
        :type service_node_id: str|unicode
        :type section_name: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `name`, `value`
            FROM `ServiceNodeEnvironment`
            WHERE `serviceNodeId` = {service_node_id} AND `section` = '{section_name}'
        """.format(service_node_id=service_node_id, section_name=section_name))
        return {
            row['name']: row['value']
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_SERVER_SETTINGS, {})
    def _get_plesk_service_node_configuration(self, service_node_id, section_name):
        """
        :type service_node_id: str|unicode
        :type section_name: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `name`, `value`
            FROM `ServiceNodeConfiguration`
            WHERE `serviceNodeId` = {service_node_id} AND `section` = '{section_name}'
        """.format(service_node_id=service_node_id, section_name=section_name))
        return {
            row['name']: row['value']
            for row in rows
        }

    @cached
    @capability_data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def _get_plesk_gl_settings(self, service_node_id):
        """
        :type service_node_id: str|unicode
        :rtype: dict[str|unicode, str|unicode]
        """
        rows = self._dump_agent.execute_sql("""
            SELECT `param`, `value`
            FROM `GL_settings`
            WHERE `serviceNodeId` = {service_node_id}
        """.format(service_node_id=service_node_id))
        return {
            row['param']: row['value']
            for row in rows
        }

    @cached
    def _get_plesk_iis_app_pool(self, owner_id, owner_type):
        """
        :type owner_id: str|unicode
        :type owner_type: str|unicode
        :rtype: parallels.plesk.source.plesk.capability_dump.model.plesk_entity.iis_app_pool.IisAppPool | None
        """
        for plesk_iis_app_pool in self._get_all_plesk_iis_app_pools().values():
            if plesk_iis_app_pool.owner_id == owner_id and plesk_iis_app_pool.owner_type == owner_type:
                return plesk_iis_app_pool
        return None

    @cached
    def _get_plesk_iis_app_pool_domains(self, iis_app_pool_id=None):
        """
        :type iis_app_pool_id: str|unicode
        :rtype: list[parallels.plesk.source.plesk.capability_dump.model.plesk_entity.iis_app_pool_domain.IisAppPoolDomain]
        """
        return [
            plesk_iis_app_pool_domain for plesk_iis_app_pool_domain in self._get_all_plesk_iis_app_pool_domains().values()
            if (not iis_app_pool_id or plesk_iis_app_pool_domain.iis_app_pool_id == iis_app_pool_id)
        ]

    def _get_domain_hosting_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        # Currently the capability dump model does not contain description of separate sites of subscription.
        # So we need to aggregate components used by all sites of subscription.
        fields = [
            'ssi', 'php', 'php_handler_id', 'perl', 'python', 'asp', 'asp_dot_net', 'managed_runtime_version',
            'webstat', 'webdeploy', 'ssl', 'certificate_id'
        ]
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_WEB_HOSTING_COMPONENTS):
            for plesk_hosting in self._get_plesk_webspace_hosting(domain_id):
                for name in fields:
                    value = getattr(plesk_hosting, name)
                    if name == 'webstat':
                        if value != 'none' and not exists(components, lambda c: c[0] == value):
                            components.append((value, 'true'))
                        continue
                    if name in ['ssl', 'certificate_id']:
                        if not exists(components, lambda c: c[0] == 'sni'):
                            components += self._get_domain_sni_components(plesk_hosting)
                        continue
                    if exists(components, lambda c: c[0] == name):
                        continue
                    if name in ['php', 'php_handler_id']:
                        if name == 'php' and value == 'true':
                            components.append(('php', 'true'))
                            components += self._get_domain_php_handler_components(plesk_hosting)
                        continue
                    if name in ['asp_dot_net', 'managed_runtime_version']:
                        if name == 'asp_dot_net' and value == 'true':
                            components.append(('asp_dot_net', 'true'))
                            components.append(('managed_runtime_version', plesk_hosting.managed_runtime_version))
                        continue
                    if value == 'true':
                        components.append((name, value))

            plesk_main_domain = self._get_all_plesk_domains().get(domain_id)
            if plesk_main_domain and plesk_main_domain.hosting_type == 'vrt_hst':
                plesk_hosting = self._get_all_plesk_hosting().get(plesk_main_domain.domain_id)
                if plesk_hosting:
                    plesk_sys_user = self._get_all_plesk_sys_users().get(plesk_hosting.sys_user_id)
                    if plesk_sys_user:
                        if plesk_sys_user.quota != '0':
                            components.append(('hard_quota', 'true'))
                        if plesk_sys_user.shell:
                            components.append(('shell', plesk_sys_user.shell))

            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                plesk_tomcat_service = self._get_plesk_domain_service(plesk_domain.domain_id, 'tomcat')
                if plesk_tomcat_service and self._get_all_plesk_webapps().get(plesk_tomcat_service.domain_service_id):
                    components.append(('tomcat', 'true'))
                    break

        return components

    def _get_domain_webmail_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        # Currently the capability dump model does not contain description of separate sites of subscription.
        # So we get webmail of main domain only.
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_WEBMAIL_COMPONENTS):
            if self._dump_agent.source_server().is_windows():
                plesk_mail_service = self._get_plesk_domain_service(domain_id, 'mail')
                if plesk_mail_service and plesk_mail_service.parameters_id != '0':
                    webmail_type = self._get_plesk_parameter(plesk_mail_service.parameters_id, 'webmail_type')
                    if webmail_type is not None:
                        components.append(('webmail', webmail_type))
            else:
                webmail = self._get_plesk_domain_parameter(domain_id, 'webmail')
                if webmail is not None:
                    components.append(('webmail', webmail))
        return components

    def _get_domain_dns_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_DNS_COMPONENTS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                if plesk_domain.dns_zone_id != '0':
                    components.append(('dns', 'true'))
                    break
        return components

    def _get_domain_mail_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_MAIL_COMPONENTS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                plesk_mail_service = self._get_plesk_domain_service(plesk_domain.domain_id, 'mail')
                if plesk_mail_service and plesk_mail_service.parameters_id != '0':
                    domain_keys_sign = self._get_plesk_parameter(plesk_mail_service.parameters_id, 'domain_keys_sign')
                    if domain_keys_sign == 'true':
                        components.append(('domain_keys', 'true'))
                        break

            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                plesk_mail_names = self._get_plesk_mail_names(domain_id=plesk_domain.domain_id)
                if plesk_mail_names:
                    components.append(('mail', 'true'))
                    break

            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                for plesk_mail_name in self._get_plesk_mail_names(domain_id=plesk_domain.domain_id):
                    if self._dump_agent.source_server().is_windows():
                        plesk_sa_mail_name = self._get_all_plesk_sa_mail_names().get('%s@%s' % (plesk_mail_name.name, plesk_domain.ascii_name))
                        if plesk_sa_mail_name and plesk_sa_mail_name.flt_enabled:
                            components.append(('spamfilter', 'true'))
                            break
                    else:
                        if plesk_mail_name.spamfilter == 'true':
                            components.append(('spamfilter', 'true'))
                            break
                if exists(components, lambda c: c[0] == 'spamfilter'):
                    break

            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                for plesk_mail_name in self._get_plesk_mail_names(domain_id=plesk_domain.domain_id):
                    if self._dump_agent.source_server().is_windows():
                        if (
                            self._get_plesk_mail_name_parameter(plesk_mail_name.mail_name_id, 'antivir_incoming') == 'true' or
                            self._get_plesk_mail_name_parameter(plesk_mail_name.mail_name_id, 'antivir_outgoing') == 'true'
                        ):
                            components.append(('antivirus', 'true'))
                            break
                    else:
                        if plesk_mail_name.virusfilter != 'none':
                            components.append(('antivirus', 'true'))
                            break
                if exists(components, lambda c: c[0] == 'antivirus'):
                    break

            if not self._dump_agent.source_server().is_windows():
                for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                    service_node_id = self._get_domain_mail_service_node_id(plesk_domain.domain_id)
                    if (
                        service_node_id is not None and
                        self._get_plesk_gl_settings(service_node_id).get('enabled') == 'true' and
                        self._get_plesk_domain_parameter(plesk_domain.domain_id, 'gl_filter', 'on') == 'on'
                    ):
                        components.append(('grey_listing', 'true'))
                        break
        return components

    def _get_domain_maillist_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_MAILLISTS_COMPONENTS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                plesk_mail_lists = self._get_plesk_mail_lists(plesk_domain.domain_id)
                if plesk_mail_lists:
                    components.append(('maillists', 'true'))
                    break
        return components

    def _get_domain_database_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_DATABASE_SERVERS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                for plesk_database in self._get_plesk_databases(plesk_domain.domain_id):
                    plesk_database_server = self._get_all_plesk_database_servers().get(plesk_database.database_server_id)
                    if (
                        plesk_database_server and
                        not exists(components, lambda c: c[0] == plesk_database_server.server_type and c[1] == plesk_database_server.version)
                    ):
                        components.append((plesk_database_server.server_type, plesk_database_server.version))
        return components

    def _get_domain_outgoing_messages_components(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_MAIL_SETTINGS):
            plesk_subscription = self._get_plesk_subscription(domain_id, 'domain')
            if plesk_subscription:
                plesk_subscription_properties = self._get_all_plesk_subscriptions_properties().get(plesk_subscription.subscription_id, {})
                for name, value in plesk_subscription_properties.items():
                    if (
                        name in {'outgoing_messages_subscription_limit', 'outgoing_messages_domain_limit', 'outgoing_messages_mbox_limit'} and
                        value != 'default' and value != ''
                    ):
                        components.append((name, 'true'))

            if not exists(components, lambda c: c[0] == 'outgoing_messages_domain_limit'):
                for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                    plesk_mail_service = self._get_plesk_domain_service(plesk_domain.domain_id, 'mail')
                    if plesk_mail_service:
                        outgoing_messages_limit = self._get_plesk_parameter(plesk_mail_service.parameters_id, 'outgoing_messages_limit')
                        if outgoing_messages_limit and outgoing_messages_limit != 'default':
                            components.append(('outgoing_messages_domain_limit', 'true'))
                            break
            if not exists(components, lambda c: c[0] == 'outgoing_messages_mbox_limit'):
                for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                    for plesk_mail_name in self._get_plesk_mail_names(domain_id=plesk_domain.domain_id):
                        outgoing_messages_limit = self._get_plesk_mail_name_parameter(plesk_mail_name.mail_name_id, 'outgoing_messages_limit')
                        if outgoing_messages_limit and outgoing_messages_limit != 'default':
                            components.append(('outgoing_messages_mbox_limit', 'true'))
                            break
                    if exists(components, lambda c: c[0] == 'outgoing_messages_mbox_limit'):
                        break
        return components

    def _get_domain_php_handler_components(self, plesk_hosting):
        """
        :type plesk_hosting: parallels.plesk.source.plesk.capability_dump.model.plesk_entity.hosting.Hosting
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_DOMAIN, self._get_domain_name(plesk_hosting.domain_id), messages.DATA_DUMP_WEB_HOSTING_SETTINGS):
            components.append(('php_handler_id', plesk_hosting.php_handler_id))
            service_node_id = self._get_domain_web_service_node_id(plesk_hosting.domain_id)
            if service_node_id is not None:
                php_handler = self._get_plesk_php_handlers(service_node_id).get(plesk_hosting.php_handler_id)
                if php_handler is not None:
                    components.append(('php_handler_type', php_handler.handler_type))
                    components.append(('php_version', php_handler.version))
        return components

    def _get_domain_sni_components(self, plesk_hosting):
        """SNI is supported since Plesk 11.5
        :type plesk_hosting: parallels.plesk.source.plesk.capability_dump.model.plesk_entity.hosting.Hosting
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        components = []
        with self._entity_data_dump(messages.ENTITY_DUMP_DOMAIN, self._get_domain_name(plesk_hosting.domain_id), messages.DATA_DUMP_WEB_HOSTING_SETTINGS):
            if plesk_hosting.ssl == 'true' and plesk_hosting.certificate_id != 0:
                components.append(('sni', 'true'))
        return components

    @cached
    @capability_data_dump(messages.DATA_DUMP_SERVER_SETTINGS, None)
    def _get_management_service_node_id(self):
        """
        :rtype: str|unicode|None
        """
        rows = self._dump_agent.execute_sql("SELECT `id` FROM `ServiceNodes` WHERE `ipAddress` = 'local'")
        if rows:
            return rows[0]['id']
        return None

    @cached
    def _get_domain_web_service_node_id(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: str|unicode|None
        """
        with self._entity_data_dump(messages.ENTITY_DUMP_DOMAIN, self._get_domain_name(domain_id), messages.DATA_DUMP_WEB_HOSTING_SETTINGS):
            domain_web_service = self._get_plesk_domain_service(domain_id, 'web')
            if domain_web_service:
                for ip_address_id in domain_web_service.ip_address_ids:
                    plesk_ip_address = self._get_all_plesk_ip_addresses().get(ip_address_id)
                    if plesk_ip_address:
                        return plesk_ip_address.service_node_id
        return None

    @cached
    def _get_domain_mail_service_node_id(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: str|unicode|None
        """
        with self._entity_data_dump(messages.ENTITY_DUMP_DOMAIN, self._get_domain_name(domain_id), messages.DATA_DUMP_MAIL_SETTINGS):
            domain_mail_service = self._get_plesk_domain_service(domain_id, 'mail')
            if domain_mail_service:
                for ip_address_id in domain_mail_service.ip_address_ids:
                    plesk_ip_address = self._get_all_plesk_ip_addresses().get(ip_address_id)
                    if plesk_ip_address:
                        return plesk_ip_address.service_node_id
        return None

    @cached
    def _get_plesk_php_handlers(self, service_node_id):
        """
        :type service_node_id: str|unicode
        :rtype: dict[str|unicode, parallels.plesk.source.plesk.capability_dump.model.plesk_entity.php_handler.PhpHandler]
        """
        php_handlers = {}
        for name, value in self._get_plesk_service_node_environment(service_node_id, 'phphandlers').items():
            if name == 'synced':
                continue
            try:
                php_handler_node = ElementTree.fromstring(value)
                php_handler_id = php_handler_node.findtext('id')
                if php_handler_id is None:
                    continue
                php_handlers[php_handler_id] = plesk_entity.PhpHandler(
                    handler_id=php_handler_id,
                    handler_type=php_handler_node.findtext('type'),
                    handler_type_name=php_handler_node.findtext('typeName'),
                    version=php_handler_node.findtext('version'),
                    display_name=php_handler_node.findtext('displayname'),
                    registered=php_handler_node.findtext('registered'),
                    enabled='true',
                )
            except Exception as e:
                logger.exception()
                Registry.get_instance().get_context().pre_check_report.add_issue(
                    'dump_capability_data',
                    Issue.SEVERITY_WARNING,
                    safe_format(messages.UNABLE_TO_GET_PHP_HANDLER_PROPERTIES, description=value, error=e)
                )

        for name, value in self._get_plesk_service_node_configuration(service_node_id, 'phphandlers').items():
            try:
                php_handler_node = ElementTree.fromstring(value)
                php_handler_id = name
                php_handler = php_handlers.get(php_handler_id)
                if php_handler is None:
                    continue
                php_handlers[php_handler_id] = plesk_entity.PhpHandler(
                    handler_id=php_handler.handler_id,
                    handler_type=php_handler.handler_type,
                    handler_type_name=php_handler.handler_type_name,
                    version=php_handler.version,
                    display_name=php_handler.display_name,
                    registered=php_handler.registered,
                    enabled=php_handler_node.findtext('enabled'),
                )
            except Exception as e:
                logger.exception()
                Registry.get_instance().get_context().pre_check_report.add_issue(
                    'dump_capability_data',
                    Issue.SEVERITY_WARNING,
                    safe_format(messages.UNABLE_TO_GET_PHP_HANDLER_PROPERTIES, description=value, error=e)
                )

        return php_handlers

    @cached
    def _is_postgresql_cli_available(self):
        """
        :rtype: bool
        """
        for command in ['pg_dump', 'psql']:
            try:
                with self._dump_agent.source_server().runner() as runner:
                    assert isinstance(runner, BaseRunner)
                    which_result = runner.execute_command(
                        'which {command}', dict(command=command), ExecutionOptions(ignore_exit_code=True)
                    ).exit_code
                    if which_result != 0:
                        return False
            except Exception as e:
                logger.exception()
                Registry.get_instance().get_context().pre_check_report.add_issue(
                    'dump_capability_data',
                    Issue.SEVERITY_WARNING,
                    safe_format(messages.UNABLE_TO_CHECK_COMMAND, command=command, error=e)
                )
        return True

    def _get_domain_disk_usage(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        domain_disk_usage = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_DISK_USAGE):
            db, vhost, mail = 0o0, 0o0, 0o0
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                disk_usage = self._get_all_plesk_disk_usage().get(plesk_domain.domain_id)
                if disk_usage:
                    db += (
                        self._convert_disk_usage_value(disk_usage.mysql_dbases) +
                        self._convert_disk_usage_value(disk_usage.mssql_dbases)
                    )
                    vhost += (
                        self._convert_disk_usage_value(disk_usage.httpdocs) +
                        self._convert_disk_usage_value(disk_usage.httpsdocs) +
                        self._convert_disk_usage_value(disk_usage.subdomains) +
                        self._convert_disk_usage_value(disk_usage.web_users) +
                        self._convert_disk_usage_value(disk_usage.anonftp) +
                        self._convert_disk_usage_value(disk_usage.webapps)
                    )
                    mail += (
                        self._convert_disk_usage_value(disk_usage.mailboxes) +
                        self._convert_disk_usage_value(disk_usage.maillists)
                    )
            domain_disk_usage += [
                ('diskusage.db', unicode(db)),
                ('diskusage.vhost', unicode(vhost)),
                ('diskusage.mail', unicode(mail)),
            ]
        return domain_disk_usage

    @staticmethod
    def _convert_disk_usage_value(value):
        if value is None:
            return 0o0
        else:
            return long(value)

    def _get_domain_account_usage(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        account_usage = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_RESOURCES_USAGE):
            aliases, mailaccounts, maillists, webusers = 0, 0, 0, 0
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                aliases += len(self._get_plesk_domain_aliases(domain_id=plesk_domain.domain_id))
                mailaccounts += len(self._get_plesk_mail_names(domain_id=plesk_domain.domain_id))
                maillists += len(self._get_plesk_mail_lists(domain_id=plesk_domain.domain_id))
                webusers += len(self._get_plesk_web_users(domain_id=plesk_domain.domain_id))
            account_usage += [
                ('resource.aliases', unicode(aliases)),
                ('resource.mailaccounts', unicode(mailaccounts)),
                ('resource.maillists', unicode(maillists)),
                ('resource.webusers', unicode(webusers)),
            ]
        return account_usage

    def _get_domain_sys_users(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        sys_users = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_SYSTEM_USERS):
            plesk_main_domain = self._get_all_plesk_domains().get(domain_id)
            if plesk_main_domain and plesk_main_domain.hosting_type == 'vrt_hst':
                plesk_hosting = self._get_all_plesk_hosting().get(plesk_main_domain.domain_id)
                if plesk_hosting:
                    plesk_sys_user = self._get_all_plesk_sys_users().get(plesk_hosting.sys_user_id)
                    if plesk_sys_user:
                        sys_users.append(('sysuser', plesk_sys_user.login))

            for plesk_web_user in self._get_plesk_ftp_users(domain_id=domain_id):
                plesk_sys_user = self._get_all_plesk_sys_users().get(plesk_web_user.sys_user_id)
                if plesk_sys_user:
                    sys_users.append(('ftpuser', plesk_sys_user.login))

            for plesk_web_user in self._get_plesk_web_users(domain_id=domain_id):
                plesk_sys_user = self._get_all_plesk_sys_users().get(plesk_web_user.sys_user_id)
                if plesk_sys_user:
                    sys_users.append(('webuser', plesk_sys_user.login))

        return sys_users

    def _get_domain_parameters(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        parameters = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_DOMAINS_SERVICES_SETTINGS):
            stat_ttl = self._get_plesk_domain_parameter(domain_id, 'stat_ttl')
            if stat_ttl is not None:
                parameters.append(('stat_ttl', stat_ttl))
        return parameters

    def _get_domain_wordpress_settings(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        wordpress_settings = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_WORDPRESS_INSTANCES):
            if self._dump_agent.source_server().plesk_major_version >= 17:
                # Starting from Plesk 17, Wordpress toolkit is a separate extension which should be
                # checked and migrated like any other extension. This check won't work for Plesk 17 and later:
                # there is no 'WordpressInstances' database table anymore
                return wordpress_settings

            has_wordpress_instances = False
            plesk_main_domain = self._get_all_plesk_domains().get(domain_id)
            if plesk_main_domain and plesk_main_domain.hosting_type == 'vrt_hst':
                plesk_subscription = self._get_plesk_subscription(plesk_main_domain.domain_id, 'domain')
                if plesk_subscription and self._get_plesk_wordpress_instances(subscription_id=plesk_subscription.subscription_id):
                    has_wordpress_instances = True
            wordpress_settings += [('hasWordPressInstances', 'true' if has_wordpress_instances else 'false')]
        return wordpress_settings

    def _get_domain_dns_settings(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        settings = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_DNS_SETTINGS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                plesk_dns_zone = self._get_all_plesk_dns_zones().get(plesk_domain.dns_zone_id)
                if plesk_dns_zone:
                    for plesk_dns_record in self._get_plesk_dns_records(dns_zone_id=plesk_dns_zone.dns_zone_id):
                        if plesk_dns_record.dns_record_type == 'AXFR' and plesk_dns_record.opt:
                            settings.append(('AXFR', 'true'))
                            break
                if exists(settings, lambda c: c[0] == 'AXFR'):
                    break
        return settings

    def _get_domain_mod_security_settings(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        mod_security_settings = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_WEB_APPLICATION_FIREWALL_SETTINGS):
            domains_on = len(self._get_plesk_webspace_hosting(domain_id))
            for plesk_hosting in self._get_plesk_webspace_hosting(domain_id):
                web_server_settings_id = self._get_plesk_domain_parameter(plesk_hosting.domain_id, 'webServerSettingsId')
                if web_server_settings_id is not None:
                    web_server_settings = self._get_all_plesk_web_server_settings().get(web_server_settings_id)
                    if web_server_settings and self._get_plesk_web_server_settings_parameter(web_server_settings.web_server_settings_id, 'ruleEngine') == 'Off':
                        domains_on -= 1
            mod_security_settings += [('modSecurityRuleEngineIsOff', 'true' if domains_on == 0 else 'false')]
        return mod_security_settings

    def _get_domain_iis_app_pool_settings(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        iis_app_pool_settings = []
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_IIS_APPLICATION_POOL_SETTINGS):
            has_iis_app_pools = False
            for plesk_hosting in self._get_plesk_webspace_hosting(domain_id):
                iis_app_pool = self._get_plesk_iis_app_pool(plesk_hosting.domain_id, 'domain')
                if iis_app_pool and self._get_plesk_iis_app_pool_domains(iis_app_pool.iis_app_pool_id):
                    has_iis_app_pools = True
                    break
            iis_app_pool_settings += [('iis_app_pool', 'true' if has_iis_app_pools else 'false')]
        return iis_app_pool_settings

    def _get_domain_postgresql_settings(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        with self._entity_data_dump(messages.ENTITY_DUMP_SUBSCRIPTION, self._get_domain_name(domain_id), messages.DATA_DUMP_POSTGRESQL_SETTINGS):
            for plesk_domain in self._get_plesk_webspace_domains(domain_id):
                for plesk_database in self._get_plesk_databases(plesk_domain.domain_id):
                    plesk_database_server = self._get_all_plesk_database_servers().get(plesk_database.database_server_id)
                    if plesk_database_server and plesk_database_server.server_type == 'postgresql':
                        return [] if self._is_postgresql_cli_available() else [('no_postgresql_commands', 'true')]
        return []

    @cached
    @capability_data_dump(messages.DATA_DUMP_SERVER_COMPONENTS, [])
    def _get_server_installed_components(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        installed_components = []
        if self._dump_agent.source_server().is_windows():
            packages_node = self._get_all_packages()
            for package_node in packages_node.findall('type/package'):
                if package_node.get('state') != 'not_installed':
                    installed_components.append(('package.%s' % package_node.get('name'), package_node.get('version')))
        else:
            service_node_id = self._get_management_service_node_id()
            if service_node_id is not None:
                for name, value in self._get_plesk_service_node_environment(service_node_id, 'componentsPackages').items():
                    if name == 'synced' or not value:
                        continue
                    installed_components.append(('package.%s' % name, value))
        return installed_components

    @capability_data_dump(messages.DATA_DUMP_PHP_HANDLERS, [])
    def _get_server_php_handler_components(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        php_handler_components = []
        service_node_id = self._get_management_service_node_id()
        if service_node_id is not None:
            for php_handler in self._get_plesk_php_handlers(service_node_id).values():
                php_handler_id = php_handler.handler_id
                php_handler_components += [
                    ('phphandler.%s.id' % php_handler_id, php_handler.handler_id),
                    ('phphandler.%s.type' % php_handler_id, php_handler.handler_type),
                    ('phphandler.%s.typeName' % php_handler_id, php_handler.handler_type_name),
                    ('phphandler.%s.version' % php_handler_id, php_handler.version),
                    ('phphandler.%s.displayname' % php_handler_id, php_handler.display_name),
                    ('phphandler.%s.registered' % php_handler_id, php_handler.registered),
                    ('phphandler.%s.enabled' % php_handler_id, php_handler.enabled),
                ]
        return php_handler_components

    @capability_data_dump(messages.DATA_DUMP_EXTENSIONS, [])
    def _get_server_installed_modules(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        installed_modules = []
        for module in self._get_all_plesk_modules().values():
            installed_modules.append(('module.%s' % module.name, module.version if module.version else module.release))
        return installed_modules

    @capability_data_dump(messages.DATA_DUMP_RESOURCES_USAGE, [])
    def _get_server_account_usage(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        accounts, domains, aliases, mailaccounts, webusers = 0, 0, 0, 0, 0
        for plesk_client in self._get_all_plesk_clients().values():
            accounts += 1
            for plesk_domain in self._get_plesk_domains(owner_id=plesk_client.client_id):
                domains += 1
                aliases += len(self._get_plesk_domain_aliases(domain_id=plesk_domain.domain_id))
                mailaccounts += len(self._get_plesk_mail_names(domain_id=plesk_domain.domain_id))
                webusers += len(self._get_plesk_web_users(domain_id=plesk_domain.domain_id))
        return [
            ('resource.accounts', accounts),
            ('resource.domains', domains),
            ('resource.aliases', aliases),
            ('resource.mailaccounts', mailaccounts),
            ('resource.webusers', webusers),
        ]

    @capability_data_dump(messages.DATA_DUMP_FTP_SETTINGS, [])
    def _get_server_ftp_settings(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        service_node_id = self._get_management_service_node_id()
        settings = []
        ftp_over_ssl = self._get_plesk_service_node_configuration(service_node_id, 'ftpServer').get('ftpOverSsl')
        if ftp_over_ssl is not None:
            settings.append(('ftpOverSsl', ftp_over_ssl))
        settings += [
            ('ftps_%s' % ip.ip_address, ip.ftps)
            for ip in self._get_all_plesk_ip_addresses().values()
        ]
        return settings

    @capability_data_dump(messages.DATA_DUMP_DNS_SETTINGS, [])
    def _get_server_dns_settings(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        settings = []
        for plesk_dns_template_record in self._get_all_plesk_dns_template_records().values():
            if plesk_dns_template_record.dns_zone_type == 'AXFR' and plesk_dns_template_record.opt:
                settings.append(('AXFR', 'true'))
                break
        return settings

    @capability_data_dump(messages.DATA_DUMP_MAIL_SETTINGS, [])
    def _get_server_mail_settings(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        service_node_id = self._get_management_service_node_id()
        settings = []
        spamfilter_use_mailuser_prefs = self._get_plesk_service_node_configuration(service_node_id, 'mailServer').get('spamfilter_use_mailuser_prefs')
        if spamfilter_use_mailuser_prefs == 'true':
            settings.append(('spamfilter_use_mailuser_prefs', spamfilter_use_mailuser_prefs))
        for limit_name in ['courier_max_connections', 'courier_max_connections_per_ip', 'dovecot_max_connections', 'dovecot_max_connections_per_ip']:
            limit_value = self._get_plesk_service_node_configuration(service_node_id, 'mailServer').get(limit_name)
            if limit_value is not None:
                settings.append((limit_name, limit_value))
        return settings

    @capability_data_dump(messages.DATA_DUMP_WEB_APPLICATION_FIREWALL_SETTINGS, [])
    def _get_server_mod_security_settings(self):
        """
        :rtype: list[tuple[str|unicode, str|unicode]]
        """
        settings = []
        service_node_id = self._get_management_service_node_id()
        web_server_settings_id = self._get_plesk_service_node_configuration(service_node_id, 'webServer').get('webServerSettingsId')
        if web_server_settings_id is not None:
            web_server_settings = self._get_all_plesk_web_server_settings().get(web_server_settings_id)
            if web_server_settings:
                rule_engine = self._get_plesk_web_server_settings_parameter(web_server_settings.web_server_settings_id, 'ruleEngine')
                if rule_engine is not None:
                    settings.append(('modSecurityRuleEngine', rule_engine))
        return settings

    @cached
    def _is_server_component_installed(self, component_name):
        """
        :type component_name: str | unicode
        :rtype: bool
        """
        return exists(self._get_server_installed_components(), lambda c: c[0] == 'package.%s' % component_name)

    @cached
    def _is_dns_enabled(self):
        """
        :rtype: bool
        """
        if self._dump_agent.source_server().is_windows():
            dns_server_node = self._get_all_packages().find("type[@name='dnsserver']")
            if dns_server_node and dns_server_node.get('default'):
                return dns_server_node.get('default') != 'none'
            return False
        else:
            return self._is_server_component_installed('bind')

    def _get_domain_name(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: str | unicode
        """
        plesk_domain = self._get_all_plesk_domains().get(domain_id)
        if plesk_domain:
            return plesk_domain.name
        return '#%s' % domain_id

    @contextmanager
    def _entity_data_dump(self, entity_type, entity_name, description=None):
        try:
            yield
        except Exception as e:
            logger.exception()
            entity_path = '%s (%s)' % (entity_type, entity_name)
            if description:
                message = safe_format(messages.FAILED_TO_DUMP_ENTITY_CAPABILITY_DATA, entity_path=entity_path, description=description, error=e)
            else:
                message = safe_format(messages.FAILED_TO_DUMP_ENTITY_CAPABILITY, entity_path=entity_path, error=e)
            Registry.get_instance().get_context().pre_check_report.add_issue(
                'dump_capability_data',
                Issue.SEVERITY_WARNING,
                message
            )
