????

Your IP : 18.220.134.161


Current Path : /proc/self/root/usr/lib/Acronis/PyShell/site-tools/
Upload File :
Current File : //proc/self/root/usr/lib/Acronis/PyShell/site-tools/amsctl.py

import acrobind
import acrort
import collections
import csv
import datetime
import json
import operator
import os.path
import prettytable
import re
import subprocess
import sys
import time
import traceback


INSTANCE_TYPES = {
    1: 'TYPE_MACHINE',
    2: 'TYPE_DB',
    3: 'TYPE_SHAREPOINT',
    4: 'TYPE_VIRTUAL_MACHINE',
    5: 'TYPE_VIRTUAL_SERVER',
    6: 'TYPE_EXCHANGE',
    7: 'TYPE_VIRTUAL_CLUSTER',
    8: 'TYPE_VIRTUAL_APPLIANCE',
    9: 'TYPE_VIRTUAL_APPLICATION',
    10: 'TYPE_VIRTUAL_RESOURCE_POOL',
    11: 'TYPE_VIRTUAL_CENTER',
    12: 'TYPE_DATASTORE',
    13: 'TYPE_DATASTORE_CLUSTER',
    14: 'TYPE_MSSQL',
    15: 'TYPE_VIRTUAL_NETWORK',
    16: 'TYPE_VIRTUAL_FOLDER',
    17: 'TYPE_VIRTUAL_DATACENTER',
    18: 'TYPE_SMB_SHARED_FOLDER',
    19: 'TYPE_MSSQL_INSTANCE',
    20: 'TYPE_MSSQL_DATABASE',
    21: 'TYPE_MSSQL_DATABASE_FOLDER',
    22: 'TYPE_MSEXCHANGE_DATABASE',
    23: 'TYPE_MSEXCHANGE_STORAGE_GROUP',
    24: 'TYPE_MSEXCHANGE_MAILBOX',
}

BACKUP_STATUSES = {
    0: 'STATUS_BACKUP_UNKNOWN',
    1: 'STATUS_BACKUP_NONE',
    2: 'STATUS_BACKUP_SUCCEEDED',
    3: 'STATUS_BACKUP_WARNING',
    4: 'STATUS_BACKUP_FAILED'
}

BACKUP_STATES = {
    0: 'BACKUP_STATE_IDLE',
    1: 'BACKUP_STATE_RUNNING',
}

INSTANCE_STATUSES = {
    0: 'INSTANCE_STATUS_UNKNOWN',
    1: 'INSTANCE_STATUS_NONE',
    2: 'INSTANCE_STATUS_SUCCEEDED',
    3: 'INSTANCE_STATUS_WARNING',
    4: 'INSTANCE_STATUS_FAILED'
}

INSTANCE_STATES = {
    0: 'IDLE',
    0x01: 'INTERACTION_REQUIRED',
    0x02: 'CANCELLING',
    0x04: 'RUNNING_BACKUP',
    0x08: 'RUNNING_RECOVER',
    0x10: 'RUNNING_INSTALL',
    0x20: 'RUNNING_REBOOT',
    0x40: 'RUNNING_FAILBACK',
    0x80: 'RUNNING_TEST',
    0x100: 'RUNNING_FROM_IMAGE',
    0x200: 'RUNNING_FINALIZE',
    0x400: 'RUNNING_FAILOVER',
    0x800: 'RUNNING_REPLICATION',
}


Log = None
args = None


def format_backtrace(exception):
    exc_type, exc_value, exc_traceback = sys.exc_info()
    info = traceback.format_exception(exc_type, exc_value, exc_traceback)
    return ''.join(line for line in info)


def error_log(message):
    if Log:
        Log.write(message)
    else:
        print(message)


def print_bool_flag(flag):
    if flag is None:
      return '-'
    if not flag:
        return '-'
    return '+'


def get_optional(object, prop_name):
    val = None
    if prop_name in object:
        val = object[prop_name].ref
    return val


def describe_tenant(tenant, full_format=True):
    if tenant is None:
        return 'MISSING'

    if full_format:
        return '\'{0}\'(Name:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(tenant.ID.ref if 'ID' in tenant else 'MISSING',\
            tenant.Name.ref if 'Name' in tenant else 'MISSING', tenant.Locator.ref if 'Locator' in tenant else 'MISSING',\
            tenant.Kind.ref if 'Kind' in tenant else 'MISSING')
    return '{}'.format(tenant.ID.ref if 'ID' in tenant else 'MISSING')


def get_tenant_string(object, full_format=True):
    if object is None or 'Tenant' not in object:
        return 'MISSING'

    return describe_tenant(object.Tenant, full_format)


def to_instance_state(state):
    if state == 0:
        return 'IDLE(0)'
    if state == 1:
        return 'RUNNING(1)'
    return 'UNKNOWN({})'.format(state)


def to_machine_status(status):
    if status == 0:
        return 'ONLINE'
    if status == 1:
        return 'OFFLINE'
    if status == 2:
        return 'FOREIGN'
    if status == 3:
        return 'EXPIRED'
    return 'UNKNOWN'


def drop_agent_connection(connection, host_id):
    try:
        argument = acrort.plain.Unit(flat=[('.ID', 'string', '{}'.format(host_id).upper())])
        activity_id = connection.tol.launch_command(command='651F1568-B113-479F-B578-A4167F9CA61B', argument=argument)
        Log.write('Waiting for command activity {} completion...'.format(activity_id), end='')
        result = connection.tol.get_result(activity_id)
        Log.write('done')
    except Exception as error:
        Log.write('Error: {}'.format(error))


def fix_item_protection_tenant(connection, ip_id, instance_id, host_id):
    print('Processing Gtob::Dto::ItemProtection with ID: {0}'.format(ip_id))
    print('Getting corresponding InstanceManagement::Instance with ID: {0}'.format(instance_id))
    (selected, instance) = acrobind.safe_select_object(connection, acrobind.create_viewspec_instance_by_id(instance_id))
    if selected and instance is None:
        print("Can't find instance with ID {0}, deleting wrong ItemProtection".format(instance_id))
        return
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(host_id))

    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(host_id))
        return
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return
    print(tenant)
    #connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.ID', ip_id), diff={'Tenant': tenant})
    print('Gtob::Dto::ItemProtection with ID {0} is fixed.'.format(ip_id))


def fix_instance_tenant(connection, instance_id, host_id):
    print('Processing InstanceManagement::Instance with ID: {0}'.format(instance_id))
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(host_id))
    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(host_id))
        return
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return
    print(tenant)
    #connection.dml.update(pattern=instance_spec(instance_id), diff={'Tenant': tenant})
    print('InstanceManagement::Instance with ID {0} is fixed.'.format(instance_id))


def fix_machine_tenant(connection, machine_id):
    print('Processing MachineManagement::Machine with ID: {0}'.format(machine_id))
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(machine_id))
    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(machine_id))
        return False
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return False
    print(tenant)
    #connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', machine_id), diff={'Tenant': tenant})
    print('MachineManagement::Machine with ID {0} is fixed.'.format(machine_id))
    return True


def analyze_tenant(object):
    if 'Tenant' in object:
        locator = '-'
        if 'Locator' in object['Tenant']:
            locator = object.Tenant.Locator.ref
        return '\'{0}\'({1})'.format(object.Tenant.Name.ref, locator)
    return None


def wrap_safe_exec_result(result):
    if not result[0]:
        return 'UNKNOWN'
    return 'OK' if result[1] else 'MISSING'


def get_objects_count(connection, machine_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id))
    return acrobind.count_objects_by_spec(connection, spec)


def get_objects_count_2(connection, machine_id):
    return acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id)))


def delete_objects_count(dml, machine_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id))
    dml.delete(pattern=spec.pattern)


def check_caching_registration(connection):
    print('Checking Dml::Sync::Caching::Registration')

    if args.machine_id is not None and args.machine_status is not None and args.machine_status == 0:


        print('Count: {0}'.format(get_objects_count(connection, args.machine_id)))

        objects = get_objects_count_2(connection, args.machine_id)
        print('Count2: {0}'.format(len(objects)))

        def caching_registration_spec(registration_id, machine_id):
            return acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.ID', registration_id), machine_id)

        object = acrobind.safe_select_object(connection, caching_registration_spec('CE030FCE-9241-400D-97C0-601610EDD186', args.machine_id))
        print('\tActivities: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('D0F82464-DD2F-4017-9745-DDEE4F44610A', args.machine_id))
        print('\tProtect command activities: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('8B94773D-6748-443B-8170-91426FD0EA98', args.machine_id))
        print('\tGtob::Protection::App::Machine: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('CC7554D7-62EA-4D57-8483-E6BFA12CDA72', args.machine_id))
        print('\tClusterManager::Cluster: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('D236945B-755A-4A79-A614-67BB674E011A', args.machine_id))
        print('\tGtob::Dto::Protection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('54EB2BDC-1465-4C34-9C94-A08C843E6ED6', args.machine_id))
        print('\tGtob::Dto::ProtectionPlan: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('BFBFEE9D-551C-4737-BF43-06CB97B7FACA', args.machine_id))
        print('\tRunVmFromImage::VMResurrection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('DE5FCF73-3A7E-4989-A869-CF61F509B0EB', args.machine_id))
        print('\tStatistics::Counters: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('B9D53478-4CB1-45C6-9EAF-91AB6DDD38CC', args.machine_id))
        print('\tReplication::Continuous::ProcessInfo: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('F47035AE-2057-4862-889A-40D6DADB7F9C', args.machine_id))
        print('\tLocal Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('055F55CC-2F09-4FB8-A5E0-63EC1F186E0F', args.machine_id))
        print('\tCentralized Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('323976BC-3CB2-4DD2-8736-9343A7B4C3DB', args.machine_id))
        print('\tLegacy Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        objects = []
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('InstanceManagement::Instance'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::Protection'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.Environment.ProtectionPlanID', 'C3A38821-AF2D-05A0-21E2-23B2F5673916'), args.machine_id))
        for o in objects:
            print('--------------')
            print(o)

    elif args.machine_id is not None:
      print('Machine is OFFLINE.')
    else:
      print('No machine ID.')


def check_running_activities(connection, spec, machine_id=None):
    Log.write('Checking activities by spec: {0}'.format(spec.pattern))

    pattern1 = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.Tenant.ID', 'string', '{}'.format(args.tenant_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
    spec1 = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern1))

    local_count = acrobind.count_objects_by_spec(connection, spec)
    Log.write('AMS has \'{}\' activities.'.format(local_count))
    if local_count > 0:
        activities = acrobind.select_objects(connection, spec)
        list_activities(activities)

        if args.fix:
            print('Do you want to change state of these activities to completed?(y/n)?')
            if ask_user():
                for a in activities:
                    id_str = '{0}'.format(a.ID.ref)
                    pattern = [
                        ('', 'dword', 5),
                    ]
                    diff_unit={'State': acrort.plain.Unit(flat=pattern)}
                    connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('Tol::History::Plain::Activity', '.ID', id_str).pattern, diff=diff_unit)
                    print('Activity {} is fixed.'.format(id_str))
            else:
                print('skipped.')

    if machine_id:
        Log.write('Checking remote activities on agent \'{0}\'.'.format(machine_id))
        local_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        local_spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=local_pattern))
        remote_spec = acrobind.viewspec_apply_remote_host(local_spec, machine_id)
        count = acrobind.count_objects_by_spec(connection, remote_spec)
        Log.write('Agent \'{}\' has \'{}\' running activities.'.format(machine_id, count))
        if count > 0:
            activities = acrobind.select_objects(connection, remote_spec)
            list_activities(activities)


def list_activities(activities):
    table = prettytable.PrettyTable(["Name", "State", "ID", "Specific", "CommandID", "HostID"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for a in activities:
        if args.extra:
            Log.write(a)
        name_str = '\'{0}\''.format(a.Name.ref)
        state_str = '{0}'.format(a.State.ref)
        id_str = '{0}'.format(a.ID.ref)
        command_id_str = '{0}'.format(a.Details.CommandID.ref)
        specific_id_str = '{0}'.format(a.Details.Specific.ref)
        host_id_str = acrobind.get_trait_value('Source', a)
        table.add_row([name_str, state_str, id_str, specific_id_str, command_id_str, host_id_str])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def delete_plan_artifacts(connection, host_id, cplan_id):
    print('Do you want to cleanup synced Gtob::Dto::ProtectionPlan(y/n)?')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id).pattern)
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', cplan_id).pattern)
        connection.dml.delete(pattern=acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id), host_id).pattern)
        print('deleted.')
    else:
        print('skipped.')


    print('Do you want to cleanup legacy  Gtob::Dto::ItemProtection(y/n)?')
    if ask_user() and host_id:
        remote_ips_spec = acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Plan', cplan_id), host_id)
        objects = acrobind.select_objects(connection, remote_ips_spec)
        for o in objects:
            print('--------------')
            print(o)

        connection.dml.delete(pattern=remote_ips_spec.pattern)
        print('deleted.')
    else:
        print('skipped.')


def origin_to_str(origin):
    if origin == 1:
        return 'L'
    if origin == 2:
        return 'C'
    if origin ==3:
        return 'D'
    return 'U'


def redeploy_plan(connection, plan_id):
    selection_state_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Gct::SelectionState', '.ID', plan_id)
    selection_state = acrobind.select_object(connection, selection_state_spec)
    print("Deleting Gtob::Gct::SelectionState: {}".format(selection_state))
    connection.dml.delete(pattern=selection_state_spec.pattern)
    digest_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ResourceDigest', '.ID', plan_id)
    digest = acrobind.select_object(connection, digest_spec)
    Log.write("Deleting Gtob::Dto::ResourceDigest: {}".format(digest))
    connection.dml.delete(pattern=digest_spec.pattern)
    deployment_request = [
      ('^Is', 'string', 'Gtob::Dto::AutomaticDeploymentRequest'),
      ('.ID', 'guid', plan_id),
      ('.ID^PrimaryKey', 'nil', None),
      ('.ID^AutomaticDeploymentRequest', 'nil', None),
      ('.InitiatedBy', 'string', 'infraview'),
      ('.Fails', 'array', [])
    ]
    #deployment_request_unit = acrort.plain.Unit(flat=deployment_request)
    #Log.write('Creating deployment request: {0}'.format(deployment_request_unit))
    #connection.dml.create(deployment_request_unit, mode=acrort.dml.CREATE_OR_REPLACE)

    Log.write('Running protect command for plan: {0}'.format(plan_id))
    plan = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', plan_id))
    tenant_connection = acrort.connectivity.Connection('ams', client_session_data = {'tenant_id': '{}'.format(plan.Tenant.ID.ref)})

    activity_id = tenant_connection.tol.launch_command(command='41830509-FCA4-4B3A-9978-3D00462DE006', argument=plan)

    result = None
    try:
        result = tenant_connection.tol.get_result(activity_id)
        tenant_connection.close()
    except acrort.Exception as ex:
        if acrort.common.interrupt_sentinel:
             tenant_connection.tol.cancel_activity(activity_id)
        Log.write('Protect canceled.')
        tenant_connection.close()
        raise


def fix_centralized_protection(connection, plan_id):
    Log.write('Creating missing Gtob::Dto::Centralized::ItemProtection object for plan \'{}\'...'.format(plan_id))
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::Centralized::ItemProtection', '.Centralized.PlanID', plan_id)
    protections = acrobind.select_objects(connection, spec)

    if not protections:
        Log.write('There are no Gtob::Dto::Centralized::ItemProtection objects for this plan')
        return

    affected_machines = []
    protected_instances = []

    for protection in protections:
        affected_machines.append([('', 'guid', protection.HostID.ref)])
        protected_instances.append(('.ProtectedInstances.{}'.format(protection.InstanceID.ref), 'dword', 1))

    centralized_protection = acrort.plain.Unit(flat=[
        ('.ActivationStatus', 'dword', 1),
        ('.AffectedMachines', 'array', affected_machines),
        ('.AffectedMachines^IsContainer', 'string', 'vector'),
        ('.AffectedMachinesCount', 'dword', 1),
        ('.CurrentFrame.BackupNumber', 'dword', 0),
        ('.CurrentFrame.LastStartTime', 'sqword', 0),
        ('.CurrentFrame.SchemeDeploymentID', 'string', ''),
        ('.CurrentFrame^Is', 'string', 'Gtob::Dto::BackupFrameData'),
        ('.Host', 'guid', '00000000-0000-0000-0000-000000000000'),
        ('.ID', 'guid', plan_id),
        ('.ID^PrimaryKey', 'nil', None),
        ('.ID^Protection', 'nil', None),
        ('.LastResult', 'dword', 0),
        ('.ManualStartNumber', 'dword', 0),
        ('.Origin', 'dword', 2),
        ('.Owner', 'string', 'root'),
        ('.PlanDeploymentState', 'dword', 0),
        ('.PlanID', 'guid', plan_id),
        ('.PlanID^ForeignKey', 'nil', None),
        *protected_instances,
        ('.Status', 'dword', 0),
        ('^Is', 'string', 'Gtob::Dto::CentralizedProtection'),
        ('^Is', 'string', 'Gtob::Dto::Protection'),
    ])
    connection.dml.create(centralized_protection)
    Log.write('Creating object:\n', centralized_protection)


def describe_plans(connection):
    Log.write('[---Checking plans---]')
    plans = None
    if args.plan_name:
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_like('Gtob::Dto::ProtectionPlan', '.Name', args.plan_name))
    if args.plan_id:
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', args.plan_id))

    check_plan_list_internal(connection, plans)

    if args.plan_id and args.change_owner:
        new_tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.change_owner))
        if new_tenant:
            diff = [
              ('^PackedDiff', 'nil', None),
              ('.DmlTimeStamp', 'sqword', 0),
              ('.DmlTimeStamp^Removed', 'nil', None),
            ]
            tenant_patch = new_tenant.merge(acrort.plain.Unit(flat=diff))
            new_owner_id = acrort.common.Guid(acrort.common.eval_md5('security-tenant-{}'.format(new_tenant.Locator.ref)))
            new_tenant_str = describe_tenant(new_tenant, full_format=True)
            plan = plans[0]
            p_tenant = get_tenant_string(plan, full_format=True)
            p_owner = get_optional(plan, "OwnerID")

            Log.write('Do you want to change owner of Gtob::Dto::ProtectionPlan from \'{0}({1})\' to \'{2}({3})\'?(y/n)'.format(p_tenant, p_owner, new_tenant_str, new_owner_id))
            if ask_user():
                owner_patch = acrort.plain.Unit({'OwnerID' : new_owner_id, 'Tenant' : tenant_patch})
                plan = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', args.plan_id))
                Log.write('Applying patch: {0}'.format(owner_patch))
                new_plan = plan.consolidate(owner_patch)
                connection.dml.create(new_plan, mode=acrort.dml.CREATE_OR_REPLACE)
                if args.extra:
                    Log.write(plan)
                Log.write('done')
        else:
            Log.write('Can\'t find tenant with ID \'{0}\''.format(args.change_owner))

    if args.plan_id and args.redeploy:
        Log.write('Do you want to redeploy Gtob::Dto::ProtectionPlan?(y/n)')
        if ask_user():
            result = redeploy_plan(connection, args.plan_id)
            Log.write(result)
            Log.write('done')

    if args.plan_id and args.fix:
        Log.write('Do you want to undeploy Gtob::Dto::ProtectionPlan?(y/n)')
        if ask_user():
            Log.write('Input host ID(for centralized plan leave it empty):')
            host_id = sys.stdin.readline()
            host_id = host_id.rstrip()

            cplan_id = None
            if not args.plan_id:
                Log.write('Input plan ID(empty ID will skip action):')
                cplan_id = sys.stdin.readline()
                cplan_id = cplan_id.rstrip()
            else:
                cplan_id = args.plan_id

            if cplan_id:
                check_plan = None
                if host_id:
                    check_plan = acrobind.select_object(connection, acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id), host_id))
                else:
                    check_plan = acrobind.select_object(connection, acrobind.create_viewspec_deployed_protection_plan_by_id(cplan_id))

                force_undeploy = False
                if not check_plan:
                    Log.write('Do you want to force undeploy?(y/n)')
                    if ask_user():
                        force_undeploy = True

                if check_plan or force_undeploy:
                    arg = acrort.common.Guid(cplan_id)
                    if args.extra:
                       Log.write(check_plan)
                    Log.write('Running unprotect')
                    if host_id:
                        activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg, target_machine=host_id)
                    else:
                        activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg)

                    try:
                        if host_id:
                            result = connection.tol.get_result(activity_id, target_machine=host_id)
                        else:
                            result = connection.tol.get_result(activity_id)
                    except acrort.Exception as ex:
                        if acrort.common.interrupt_sentinel:
                            if host_id:
                                connection.tol.cancel_activity(activity_id, target_machine=host_id)
                            else:
                                connection.tol.cancel_activity(activity_id)
                        Log.write('Unprotect canceled.')
                        delete_plan_artifacts(connection, host_id, cplan_id)
                        raise
                    Log.write(result)
                    Log.write('done')

                    delete_plan_artifacts(connection, host_id, cplan_id)
                else:
                    Log.write('Plan not found. Skipped.')

                    delete_plan_artifacts(connection, host_id, cplan_id)
            else:
                Log.write('skipped')
        else:
            Log.write('skipped')

    if args.plan_id and args.fix_centralized_protection:
        plan = plans[0]
        is_centralized = (plan.Origin.ref == 2)
        cprotection = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', args.plan_id))
        if cprotection:
            Log.write('Gtob::Dto::CentralizedProtection already exist')
        else:
            if not is_centralized:
                Log.write('Gtob::Dto::CentralizedProtection can be created for centralized plans only')
            else:
                Log.write('Do you want to create missing Gtob::Dto::CentralizedProtection?(y/n)')
                if ask_user():
                    fix_centralized_protection(connection, args.plan_id)
                    Log.write('done')


def list_tenants(connection):
    Log.write('[---List of tenants that matches \'{}\'---]'.format(args.tenant_name))
    objects = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_like('Tenants::HierarchyNode', '.Name', args.tenant_name))

    table = prettytable.PrettyTable(["Name", "ID", "Locator", "Kind"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for o in objects:
        if args.extra:
            Log.write(o)
        o_name = '\'{0}\''.format(o.Name.ref)
        o_id_str = '{0}'.format(o.ID.ref)
        o_locator_str = '{0}'.format(o.Locator.ref)
        o_kind_str = '{0}'.format(o.Kind.ref)
        table.add_row([o_name, o_id_str, o_locator_str, o_kind_str])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def handle_remove_object_request(connection, spec):
    if args.delete:
        print('Do you want to delete object by spec(y/n):\n\'{0}\'?'.format(spec.pattern))
        if ask_user():
            connection.dml.delete(pattern=spec.pattern)
            print('done')
        else:
            print('skipped')


def fix_centralized_protections(connection, plans):
    centralized_plan_found = False
    for plan in plans:
        try:
            # Process centralized plans onnly
            if plan.Origin.ref != 2:
                continue

            centralized_plan_found = True
            cprotection_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', plan.ID.ref)
            cprotection = acrobind.select_object(connection, cprotection_spec)

            if cprotection:
                Log.write('Gtob::Dto::CentralizedProtection for plan \'{}\' already exists, skipping'.format(plan.ID.ref))
            else:
                fix_centralized_protection(connection, plan.ID.ref)
        except Exception as e:
            error_log(format_backtrace(e))

    if not centralized_plan_found:
        Log.write('There are no centralized plans for current account, nothing to fix')
    Log.write('done')


def check_plan_list(connection):
    Log.write('[---Checking plans---]')
    plans = []

    ids = [
'9248C82C-3848-430B-A74E-A2B3490112B9',
'DC5BB2CF-F645-43A1-9F6D-F77EAC8DABCA',
'099E59F7-856B-47D6-B6C6-87F61B25E907',
'F1EFC07B-B6F7-45A6-8B0B-439ADBBCD73E',
'74C6AFDB-682D-454F-BCCA-F36656EBF991',
'0FD9477E-69ED-42C8-B7BE-B7F25C76814A',
'29FE3C53-C0BB-47B4-87E7-269030FBF754',
'5B01139F-97CF-4957-969D-AFFBA5FB2A13',
'D7DF4A24-00BB-407F-9AA1-A54476953466',
'8434793F-9745-4855-A38F-53F8F42B6674',
'7D0F79F7-0551-4154-A463-D689514D14F6',
'C382E30D-66B2-41D9-A9EA-7AA4C08405F7',
'7D5EA15A-F928-441E-960F-CAE5EBE96603',
'C47E4AB4-9D37-41AF-9C1D-4681A96892CC',
'127F8D2C-A4B2-4FED-9A52-0EBBA608D25B',
'F77882CF-4E45-4A95-B3E8-6061E7BE5A98',
'60B724D9-B0A2-4916-91E7-C31C72D31DDE',
'03E1BBA6-1F6C-4446-926D-E5D997E412A6',
'4F8F9FCE-299D-4493-8E97-4F98299D15BD',
'25921214-FF3C-4A4B-BA0E-D50D2E994E9E',
'26FE004C-4773-4D9E-9F55-257D3F8E10FA',
'807BDBD3-DDD1-4EEB-BC99-5C6B63453BBD',
'99A1B1C0-089F-490F-8865-DE38A82CF5BC',
'88C98A57-0537-4F55-A5E9-E21E3999516D',
'19E90A70-1BAB-4929-8BB2-2038AF58F718',
'998EE36E-4973-496D-B8A2-B14AA3ECD526',
'1584C104-F6B1-42A4-A62B-D1B1DDB23330',
'34742B08-07EE-488D-BB8C-67CE062EDC67',
'EB333349-4FA2-4822-ADBC-59E129AD02A3',
'3D382519-9E62-42A4-A302-EB42AB493856',
'34D86CC4-76B8-43C2-B87B-F29D660A3F1F',
'CF9B8B40-9AA5-450A-8A50-6C8987733558',
'49791B80-D074-488D-9578-A8B4FC81F483',
'E65223EC-BDAB-4EE0-BA8A-55EDF94AC0D7',
'952B2C76-B4AF-4636-B845-EDA9E7322940',
'CF7BFC0C-F2D0-424D-A4EA-2E36EC5604B5',
'7547E6FF-AE84-4EB4-A57A-E9BCB51AE8E6',
'1E150441-5993-45CF-85DA-100738092AEC',
'05FAB142-3952-4DB0-83A4-8140FEFEF498',
'DC7A5FE6-0E4D-4906-A990-0E3EED7FEF0C',
'BDDF59BE-4CB7-46F4-9B28-29EC5F735394',
'29DBFE77-7B92-4AEE-9633-F7A4417AF560',
'7518FB93-56F0-418D-AC70-645D0A005302',
'F9FF7DA1-D21D-459F-8ABA-D1C51C93ED04',
'13215CC3-10C2-43A6-B773-CABEE0E9BA0C',
'730B3DA3-E118-4C1A-8C12-2F4F9D246C83',
'781AF1F6-0EC4-48CD-A717-B07CEF53CF1A',
'1E48C701-548C-4D7D-9B1D-3E06D99AA86E'
    ]
    for id in ids:
        pattern = [
          ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
          ('.ID', 'guid', id)
        ]
        object = acrobind.select_object(connection, acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern)))
        if object is not None:
            plans.append(object)

    check_plan_list_internal(connection, plans)


def check_plan_list_internal(connection, plans):

    plan_table = prettytable.PrettyTable(["Index", "Name/Instance", "ID", "Agents", "Status", "Tenant"])
    plan_table.align["Name/Instance"] = "l"
    plan_table.align["Status"] = "l"
    plan_table.padding_width = 1

    is_centralized = False
    p_owner = None
    p_tenant = None
    p_id = None
    affected_machines_str = '-'
    affected_machines_ids_str = None
    centralized_plan_id_str = '-'
    cprotection = None


    index = 0
    for p in plans:
        if args.extra:
            Log.write(p)
        p_id = p.ID.ref
        p_id_str = '{0}'.format(p_id)
        p_origin = origin_to_str(p.Origin.ref)
        p_name = '\'{0}\'({1})'.format(p.Name.ref, p_origin)

        p_owner = get_optional(p, "OwnerID")

        if p.Origin.ref == 2: #'CENTRALIZED'
            is_centralized = True

            affected_machine_count = None
            cprotection = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', p_id))

            if not cprotection:
                Log.write('Missing centralized protection for plan \'{0}\'!'.format(p_id))
                continue

            if args.extra:
                print(cprotection)

            affected_machines_str = 'MISSING'
            if 'AffectedMachines' in cprotection:
                affected_machines_str = '{0}'.format(len(cprotection.AffectedMachines))

                for (number, affected_machine) in cprotection.AffectedMachines:
                    if affected_machines_ids_str:
                        affected_machines_ids_str = '{0}, {1}'.format(affected_machines_ids_str, affected_machine.ref)
                    else:
                        affected_machines_ids_str = '{0}'.format(affected_machine.ref)
        else:
            centralized_plan_id_str = '{0}'.format(p.CentralizedProtectionPlan.ref)

        p_source = ''
        for tname, tunit in p.traits:
            if tname == 'Source':
                p_source = '{0}'.format(tunit.ref)
                break

        p_tenant = get_tenant_string(p, full_format=True)
        t = get_tenant_string(p, full_format=False)


        items = []
        if is_centralized:
            items = check_cplan_related_item_protections(connection, p_id)
        else:
            items = check_plan_related_item_protections(connection, p_id)

        plan_table.add_row([index, p_name, p_id_str, affected_machines_str, '', t])
        index += 1
        for item in items:
            #item['lfi_status'] = None
            #item['lfi_result'] = None
            #item['lfi_time'] = None
            #item['ns_time'] = None
            #item['lsi_time'] = None
            #item['cpi'] = '-'
            #item['lpi'] = '-'
            #item['legacy_pi'] = '-'

            errors_string = ''
            errors_count = len(item['lfi_errors'])
            warnings_string = ''
            warnings_count = len(item['lfi_warnings'])
            is_ok = True
            for error in item['lfi_errors']:
                if errors_string != '':
                    errors_string += '\n{}'.format(errors_string)
                else:
                    errors_string = error

            for warning in item['lfi_warnings']:
                if warnings_string != '':
                    warnings_string += '\n{}'.format(warnings_string)
                else:
                    warnings_string = warning

            last_result = ''
            if errors_count > 0:
                last_result = 'E({}): {}'.format(errors_count, errors_string)
            if warnings_count > 0:
                if errors_count > 0:
                    last_result += '\n'
                last_result += 'W({}): {}'.format(warnings_count, warnings_string)

            if errors_count == 0 and warnings_count == 0:
                last_result = 'OK'
            elif args.parameter1 == 'id':
                last_result = 'E:{},W:{}'.format(errors_count, warnings_count)
            status = '{}\nLFT: {}\nNST: {}'.format(last_result, item['lfi_time'], item['ns_time'])
            #item_tenant_str = '{}({})'.format(item['tenant_id'], item['tenant_name'])
            item_tenant_str = '{}'.format(item['tenant_id'])

            plan_table.add_row([index, '  |- {}'.format(item['instance_id']), item['id'], item['host_id'], status, item_tenant_str])
            index += 1

    #Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "ID" "Agents", "Status", "Tenant"]))
    if args.parameter1 == 'id':
        Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "ID", "Agents", "Status", "Tenant"]))
    else:
        Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "Status", "Tenant"]))
    #print(plan_table)



def check_tenant(connection):
    Log.write('[---Checking tenant \'{}\'---]'.format(args.tenant_id))

    tenant_spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.tenant_id)
    t = acrobind.select_object(connection, tenant_spec)
    if args.extra:
        Log.write(t)

    if not t:
        Log.write('No such tenant.')
        return

    t_name = '\'{0}\'(ID:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(t.Name.ref, t.ID.ref, t.Locator.ref, t.Kind.ref)
    Log.write('Tenant match: {0}'.format(t_name))

    handle_remove_object_request(connection, tenant_spec)

    if args.update:
        is_arr = [None, 'InstanceManagement::Instance', 'MachineManagement::Machine', 'Tol::History::Plain::Activity']
        base_pattern = [
            ('.Tenant.ID', 'string', t.ID.ref)
        ]
        raw_diff = acrort.plain.UnitDiff()
        raw_diff.add_patch('.Tenant.Name', t.Name.ref)
        for is_str in is_arr:
            pattern = base_pattern.copy()
            if is_str:
                pattern.append(('^Is', 'string', is_str))
            connection.dml.update(pattern=acrort.plain.Unit(flat=pattern), raw_diff=raw_diff)
        Log.write('All tenant sections is successfully updated.')

    if args.running_activities:
        pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.Tenant.ID', 'string', '{}'.format(args.tenant_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))
        check_running_activities(connection, spec)

    if args.machine_statistics:
        stat = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('MachineManagement::Statistics', '.Tenant.ID', args.tenant_id))
        Log.write(stat)

    msp_machine_table = prettytable.PrettyTable(["AgentID", "OwnerID", "IsEnabled", "PublicKey"])
    msp_machine_table.align["AgentID"] = "l"
    msp_machine_table.padding_width = 1

    machine_table = prettytable.PrettyTable(["Name", "ID", "Status", "InsideVirtual", "Tenant"])
    machine_table.align["Name"] = "l"
    machine_table.padding_width = 1

    plan_table = prettytable.PrettyTable(["Name", "ID", "Origin", "Source", "Tenant"])
    plan_table.align["Name"] = "l"
    plan_table.padding_width = 1

    tenants = []
    if t.Kind.ref != -1:
        Log.write('Checking subtenants of group...')
        pattern = [
            ('^Is', 'string', 'Tenants::HierarchyNode'),
            ('.Locator', 'string', ''),
            ('.Locator^DirectRelative', 'string', t.Locator.ref)
        ]
        tenants.extend(acrobind.select_objects(connection, acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))))
    else:
        tenants.append(t)

    all_plans = []
    for t in tenants:
        if t.ID.ref != args.tenant_id:
            t_name = '\'{0}\'(ID:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(t.Name.ref, t.ID.ref, t.Locator.ref, t.Kind.ref)
            Log.write('Subtenant: {0}'.format(t_name))

        msp_machines = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.OwnerID', t.ID.ref))
        machines = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('MachineManagement::Machine', '.Tenant.ID', t.ID.ref))
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('Gtob::Dto::ProtectionPlan', '.Tenant.ID', t.ID.ref))
        all_plans.extend(plans)

        add_tenant_description(connection, t, msp_machine_table, msp_machines, machine_table, machines, plan_table, plans)

    Log.write(msp_machine_table)
    Log.write('')
    Log.write(machine_table)
    Log.write('')
    Log.write(plan_table.get_string(sortby="Name"))
    Log.write('')

    if args.fix_centralized_protection:
        Log.write('Do you want to fix Gtob::Dto::CentralizedProtection objects for account and its children?(y/n)')
        if not ask_user():
            return
        fix_centralized_protections(connection, all_plans)

    if args.check_multitenancy:
        Log.write('Check virtual instances with mixed ownership...')
        instances = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('InstanceManagement::Instance', '.Tenant.ID', t.ID.ref))

        if not instances:
            Log.write('Tenant doesn\'t have virtual instances')
            return

        all_vcenters = set()
        all_hosts = set()

        hosts_to_select = set()
        vcenters_to_select = set()

        already_selected = {instance.ID.ref for instance in instances}

        # Collect IDs of hosts and vcenters referened in virtual machines and servers
        for instance in instances:
            instance_id = instance.ID.ref
            instance_type = INSTANCE_TYPES.get(instance.Type.ref)

            if instance_type == 'TYPE_VIRTUAL_MACHINE':
                if instance.get_branch('.Parameters.Server', None) is not None:
                    host_id = instance.Parameters.Server[0].ref
                    if host_id not in already_selected:
                        hosts_to_select.add(host_id)
                    all_hosts.add(host_id)

            if instance_type == 'TYPE_VIRTUAL_SERVER':
                all_hosts.add(instance.ID.ref)
                if instance.get_branch('.Parameters.VirtualCenterUUID', None) is not None:
                    vcenter_id = instance.Parameters.VirtualCenterUUID[0].ref
                    if vcenter_id not in already_selected:
                        vcenters_to_select.add(vcenter_id)
                    all_vcenters.add(vcenter_id)

            if instance_type == 'TYPE_VIRTUAL_CENTER':
                all_vcenters.add(instance.ID.ref)

        def values_pattern(property, values):
            return [
                ('^Is', 'string', 'InstanceManagement::Instance'),
                (property, 'guid', '00000000-0000-0000-0000-000000000000'),
                (property + '^ValueIn', 'complex_trait', [
                    ('', 'array', [[('', 'guid', value)] for value in values])
                ]),
            ]

        def searchable_values_pattern(property, values):
            return [
                ('^Is', 'string', 'InstanceManagement::Instance'),
                (property, 'array', []),
                (property + '^HasIntersection', 'complex_trait', [
                    ('', 'array', [[('', 'string', str(value))] for value in values]),
                ])
            ]

        instances = []

        # Select all vcenters referenced by tenant hosts
        if vcenters_to_select:
            instances.extend(acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(values_pattern('.ID', vcenters_to_select))))
        # Select all hosts that have reference to all found vcenters
        if all_vcenters:
            pattern = searchable_values_pattern('.Parameters.VirtualCenterUUID', all_vcenters)
            hosts_by_vcenters = acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(pattern))

            for host in hosts_by_vcenters:
                host_id = host.ID.ref
                all_hosts.add(host_id)
                if host_id in hosts_to_select:
                    hosts_to_select.remove(host_id)

            instances.extend(hosts_by_vcenters)

        # Select all hosts referenced by tenant vms
        if hosts_to_select:
            instances.extend(acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(values_pattern('.ID', hosts_to_select))))
        # Select all vms that have reference to all found hosts
        if all_hosts:
            pattern = searchable_values_pattern('.Parameters.Server', all_hosts)
            vms = acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(pattern))
            instances.extend(vms)

        found = False
        for instance in instances:
            if instance.Tenant.ID.ref != args.tenant_id:
                if not found:
                    Log.write('List of virtual instances with mixed ownership:\n')
                    found = True
                Log.write('ID: %s' % instance.ID.ref)
                Log.write('Name: %s' % instance.FullPath.ref)
                Log.write('Type: %s' % INSTANCE_TYPES.get(instance.Type.ref))
                Log.write('Owner ID: %s' % instance.Tenant.ID.ref)
                Log.write('Owner Name: %s\n' % instance.Tenant.Name.ref)

        if not found:
            Log.write('All virtual instances belong to the same tenant')


def add_tenant_description(connection, t, msp_machine_table, msp_machines, machine_table, machines, plan_table, plans):
    for msp in msp_machines:
        if args.extra:
            print(msp)
        public_key = '{}'.format(msp.PublicKey.ref) if 'PublicKey' in msp else 'Missing'
        msp_machine_table.add_row(['{}'.format(msp.AgentID.ref), '{}'.format(msp.OwnerID.ref), '{}'.format(msp.IsEnabled.ref), public_key])

    for m in machines:
        if args.extra:
            Log.write(m)
        tenant_info = get_tenant_string(m, full_format=False)
        is_inside_virtual = ''
        if 'IsInsideVirtual' in m.Info:
            is_inside_virtual = '{}'.format(m.Info.IsInsideVirtual.ref)
        machine_table.add_row([m.Info.Name.ref, '{}'.format(m.ID.ref), to_machine_status(m.Status.ref), is_inside_virtual, tenant_info])

    for p in plans:
        if args.extra:
            Log.write(p)
        p_name = '\'{0}\''.format(p.Name.ref)
        p_id = p.ID.ref
        p_id_str = '{0}'.format(p_id)
        p_origin = origin_to_str(p.Origin.ref)
        p_source = ''
        for tname, tunit in p.traits:
            if tname == 'Source':
                p_source = '{0}'.format(tunit.ref)
                break

        t_name = get_tenant_string(p, full_format=False)
        plan_table.add_row([p_name, p_id_str, p_origin, p_source, t_name])


def quotas_reconcile(connection):
    def tail(file):
        while True:
            line = file.readline().strip()
            if not line:
                time.sleep(0.1)
                continue
            yield line

    def create_request():
        Log.write('Create request for quotas reconsiling.')
        request = acrort.plain.Unit(flat=[
            ('.Tenant.ID', 'string', args.tenant_id),
            ('.Tenant.ID^PrimaryKey', 'nil', None),
            ('.Tenant.ID^Type', 'string', 'Gtob::Protection::ResourceUsage::ForceAcquireRequest'),
            ('^Is', 'string', 'Gtob::Protection::ResourceUsage::ForceAcquireRequest')
        ])
        connection.dml.create(request)

    log_file = open('/var/lib/Acronis/AMS/logs/resource-usage.0.log')
    log_file.seek(0, 2)

    create_request()

    error_count = 0
    for line in tail(log_file):
        col = line.split(' ')
        if len(col) < 5:
            continue
        col[4] = ' '.join(col[4:])
        col = col[:5]

        reconcile_prefix = '[Reconcile'
        start_line = '[Reconcile] account: \'{}\'. Start.'.format(args.tenant_id)
        finish_line = '[Reconcile] account: \'{}\'. Finish.'.format(args.tenant_id)

        if col[4] == start_line:
            Log.write(' '.join([col[0], col[1], col[4]]))
        if reconcile_prefix in col[4] and col[3][0] == 'E':
            Log.write(' '.join([col[0], col[1], col[4]]))
            error_count += 1
        if col[4] == finish_line:
            Log.write(' '.join([col[0], col[1], col[4]]))
            break
    Log.write('\nScript finished. Error count: \'{}\''.format(error_count))


def list_machines(connection):
    print('[---List of machines that match name \'{}\'---]'.format(args.machine_name))
    machine_spec = acrobind.create_viewspec_by_is_and_like('MachineManagement::Machine', '.Info.Name', args.machine_name)
    machines = acrobind.select_objects(connection, machine_spec)

    table = prettytable.PrettyTable(["Name", "ID", "Tenant"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for m in machines:
        if args.extra:
            Log.write(m)
        m_name = '\'{0}\''.format(m.Info.Name.ref)
        m_id = m.ID.ref
        m_id_str = '{0}'.format(m_id)
        t = get_tenant_string(m)
        table.add_row([m_name, m_id_str, t])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def list_instances(connection):
    Log.write('[---List of instances that match name \'{}\'---]'.format(args.instance_name))
    spec = acrobind.create_viewspec_by_is_and_like('InstanceManagement::Instance', '.FullPath', args.instance_name)
    objects = acrobind.select_objects(connection, spec)
    list_instances_internal(connection, objects)


def list_instances_internal(connection, objects):

    for o in objects:
        table = prettytable.PrettyTable(["Name", "Value", ""])

        table.align["Name"] = "l"
        table.align["Value"] = "l"
        table.padding_width = 1
        if args.extra:
            Log.write(o)
        o_name = '\'{0}\''.format(str(get_optional(o, 'FullPath')))
        o_id_str = '{0}'.format(o.ID.ref)
        o_host_id_str = '{0}'.format(o.HostID.ref)
        o_state_str = to_instance_state(get_optional(o, 'State'))
        o_backup_state_str = to_instance_state(o.BackupState.ref)
        o_status = '{0}'.format(o.Status.ref)
        o_backup_status_str = '{0}'.format(o.BackupStatus.ref)
        o_inside_virtual_str = 'MISSING'
        if 'HostInfo' in o:
            o_inside_virtual_str = '{0}'.format(get_optional(o.HostInfo, "IsInsideVirtual"))
        availability_str = 'MISSING'
        if 'Availability' in o:
            availability_str = '{0}'.format(to_machine_status(o.Availability.ref))
        last_backup_str = '-'
        if 'LastBackup' in o:
            last_backup_str = describe_time(o.LastBackup.ref)
        last_backup_try_str = '-'
        if 'LastBackupTry' in o:
            last_backup_try_str = describe_time(o.LastBackupTry.ref)
        next_backup_time_str = '-'
        if 'NextBackupTime' in o and 'Time' in o.NextBackupTime:
            next_backup_time_str = describe_time(o.LastBackup.ref)
        t = get_tenant_string(o, full_format=False)
        table.add_row(['Name', o_name, ''])
        table.add_row(['ID', o_id_str, ''])
        table.add_row(['HostID', o_host_id_str, ''])
        table.add_row(['State', o_state_str, ''])
        table.add_row(['Status', o_status, ''])
        table.add_row(['State(Backup)', o_backup_state_str, ''])
        table.add_row(['Status(Backup)', o_backup_status_str, ''])
        table.add_row(['LastBackup', last_backup_str, ''])
        table.add_row(['LastBackupTry', last_backup_try_str, ''])
        table.add_row(['NextBackupTime', next_backup_time_str, ''])
        table.add_row(['Availability', availability_str, ''])
        table.add_row(['InsideVirtual', o_inside_virtual_str, ''])
        table.add_row(['Tenant', t, ''])

        Log.write(table)
        Log.write('')


def check_instance(connection):
    Log.write('Checking instance \'{}\'...'.format(args.instance_id))

    local_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', args.instance_id)
    Log.write('AMS instance:')
    object = acrobind.select_object(connection, local_spec)
    if object is not None:
        list_instances_internal(connection, [object])
    else:
        Log.write('Not found')
        return

    spec = acrobind.viewspec_apply_remote_host(local_spec, object.HostID.ref)
    Log.write('Agent instance:')
    try:
        objects = acrobind.select_objects(connection, spec)
        list_instances_internal(connection, objects)
    except Exception as e:
        if args.extra:
            Log.write('Failed to get instance from agent: {}'.format(e))
        else:
            Log.write('Failed to get instance from agent')
    items = check_instance_related_item_protections(connection, args.instance_id)
    #TODO:
    print(items)


def check_machine(connection):
    if not args.delete and args.break_connection and args.machine_id:
        drop_agent_connection(connection, args.machine_id)

    Log.write('[---Check machine with id \'{}\'---]'.format(args.machine_id))
    machine_spec = acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id)
    machine = acrobind.select_object(connection, machine_spec)

    if not machine:
        Log.write('Not found.')
        #return

    if args.extra:
        Log.write(machine)

    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(args.machine_id)))
    public_key = 'MISSING'
    is_enabled = 'MISSING'
    if msp_machine and 'PublicKey' in msp_machine:
        public_key = '{0}'.format(msp_machine.PublicKey.ref)
    if msp_machine and 'IsEnabled' in msp_machine:
        is_enabled = '{0}'.format(msp_machine.IsEnabled.ref)

    if args.extra:
        Log.write(msp_machine)

    machine_table = prettytable.PrettyTable(["Name", "Value", ""])

    machine_table.align["Name"] = "l"
    machine_table.align["Value"] = "l"
    machine_table.padding_width = 1

    name_str = '-'
    if machine and 'Info' in machine and 'Name' in machine.Info:
        name_str = '{}'.format(machine.Info.Name.ref)
    machine_table.add_row(['Name', name_str, ''])

    id_str = '-'
    if machine and 'ID' in machine:
        id_str = '{}'.format(machine.ID.ref)

    machine_table.add_row(['ID', id_str, ''])

    status_str = '-'
    if machine and 'Status' in machine:
        machine_statuses = {0: 'ONLINE (0)', 1: 'OFFLINE (1)'}
        if machine.Status.ref in machine_statuses:
            status_str = machine_statuses[machine.Status.ref]
        else:
            status_str = str(machine.Status.ref)

    machine_table.add_row(['Status', status_str, ''])
    machine_table.add_row(['PublicKey', public_key, ''])
    machine_table.add_row(['IsEnabled', is_enabled, ''])
    is_inside_virtual_str = '-'

    if machine and 'Info' in machine and 'IsInsideVirtual' in machine.Info:
        is_inside_virtual_str = '{}'.format(machine.Info.IsInsideVirtual.ref)

    machine_table.add_row(['IsInsideVirtual', is_inside_virtual_str, ''])
    machine_table.add_row(['Tenant', get_tenant_string(machine), ''])

    current_version = 'Unknown'
    try:
        if 'Info' in machine:
            current_version = machine.Info.Agents[0].Version.ref
    except:
        pass

    installed_agents = 'unknown'
    if machine and 'Info' in machine:
        if is_mobile_agent(machine.Info.Agents):
            installed_agents = 'mobile {}'.format(installed_agents)

        if is_ad_agent(machine.Info.Agents):
            installed_agents = 'ad {}'.format(installed_agents)

        if is_ati_agent(machine.Info.Agents):
            installed_agents = 'ati {}'.format(installed_agents)

        if is_win_agent(machine.Info.Agents):
            installed_agents = 'win {}'.format(installed_agents)

        if is_sql_agent(machine.Info.Agents):
            installed_agents = 'sql {}'.format(installed_agents)

        if is_esx_agent(machine.Info.Agents):
            installed_agents = 'esx {}'.format(installed_agents)

        if is_hyperv_agent(machine.Info.Agents):
            installed_agents = 'hyperv {}'.format(installed_agents)

        if is_exchange_agent(machine.Info.Agents):
            installed_agents = 'exchange {}'.format(installed_agents)

        if not installed_agents:
          Log.write(machine.Info.Agents)

    machine_table.add_row(['Current version', '{} | {}'.format(current_version, installed_agents), ''])

    update_desc = '-'
    if machine and 'UpdateState' in machine and 'UpdateIsAvailable' in machine.UpdateState:
        if machine.UpdateState.UpdateIsAvailable.ref:
            update_desc = '{}'.format(machine.UpdateState.UpdateVersion.ref)
    update_url = ''
    if machine and 'UpdateState' in machine and 'UpdateUrl' in machine.UpdateState:
        update_url = '{}'.format(machine.UpdateState.UpdateUrl.ref)
    machine_table.add_row(['Available update', '{}'.format(update_desc), update_url])
    Log.write(machine_table)

    if machine and 'Tenant' not in machine and args.fix:
        Log.write('Do you want to fix Tenant info for machine object with ID \'{0}\'(y/n)?'.format(args.machine_id))
        if ask_user():
            fix_machine_tenant(connection, args.machine_id)
            Log.write('done')
        else:
            Log.write('skipped')

    Log.write('Instances:')
    instances_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.HostID', args.machine_id)
    objects = acrobind.select_objects(connection, instances_spec)
    list_instances_internal(connection, objects)

    #check_caching_registration(connection)

    if args.running_activities:
        pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('^Source', 'string', '{}'.format(args.machine_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))
        check_running_activities(connection, spec, args.machine_id)

    if args.reset_update:
        Log.write('Reseting update status for MachineManagement::Machine with ID {0}.'.format(args.machine_id))
        pattern = [
            ('.MachineIsProcessed', 'bool', False),
        ]
        diff_unit={'UpdateState': acrort.plain.Unit(flat=pattern)}
        #diff_unit={'Status': 1}
        connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id).pattern, diff=diff_unit)

    if args.delete:
        do_deletion(connection)


def ask_user():
    answer = sys.stdin.readline()
    if answer.startswith('y') or answer.startswith('Y'):
        return True
    else:
        return False

    return False

def wrap_error(error):
    if error == acrort.common.SUCCESS:
        return 'OK'
    return 'E'


def describe_time(time):
    return datetime.datetime.fromtimestamp(time).strftime('%Y-%m-%d %H:%M:%S')


def check_instance_related_item_protections(connection, instance_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.InstanceID', instance_id)
    return check_item_protection_objects_internal(connection, spec)

def check_plan_related_item_protections(connection, plan_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Local.PlanID', plan_id)
    return check_item_protection_objects_internal(connection, spec)


def check_cplan_related_item_protections(connection, cplan_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Centralized.PlanID', cplan_id)
    return check_item_protection_objects_internal(connection, spec)


def check_item_protection_objects_internal(connection, spec):
    ips = acrobind.select_objects(connection, spec)
    items = []
    if not (len(ips) > 0):
        return items

    for ip in ips:
        if args.extra:
            print(ip)

        item = {}
        item['tenant_id'] = get_tenant_string(ip, full_format=False)
        item['tenant'] = get_tenant_string(ip, full_format=True)
        item['tenant_name'] = ip.Tenant.Name.ref
        item['id'] = '{0}'.format(ip.ID.ref)
        item['instance_id'] = '{0}'.format(ip.InstanceID.ref)
        item['host_id'] = '-'
        if 'HostID' in ip:
            item['host_id'] = '{0}'.format(ip.HostID.ref)

        instance_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', ip.InstanceID.ref)
        instance_spec = acrobind.viewspec_apply_mask(instance_spec, acrobind.create_mask3('.ID', '.FullPath', '.Type'))
        instance = acrobind.select_object(connection, instance_spec)
        if instance:
            #print(instance)
            item['instance_id'] = '{}({})'.format(instance.FullPath.ref, instance.Type.ref)

        item['lfi_status'] = None
        item['lfi_result'] = None
        item['lfi_result_full'] = None
        item['lfi_errors'] = []
        item['lfi_warnings'] = []
        item['lfi_time'] = None
        item['ns_time'] = None
        item['lsi_time'] = None
        item['cpi'] = '-'
        item['lpi'] = '-'
        item['legacy_pi'] = '-'

        if 'LastFinishInfo' in ip:
            item['lfi_status'] = '{0}'.format(ip.LastFinishInfo.Status.ref)
            item['lfi_result'] = '-'
            if 'CompletionResult' in ip.LastFinishInfo:
                item['lfi_result'] =  '{0}'.format(wrap_error(ip.LastFinishInfo.CompletionResult))
                item['lfi_result_full'] =  '{0}'.format(ip.LastFinishInfo.CompletionResult.ref)

            item['lfi_time'] =  '{0}'.format(describe_time(ip.LastFinishInfo.Time.ref))

            str_limit = 70
            if 'Errors' in ip.LastFinishInfo:
                for index, error in ip.LastFinishInfo.Errors:
                    full_error_str = '{}'.format(error.Error.ref)
                    last_error_pos = full_error_str.rfind('| error')
                    last_error_pos_2 = full_error_str.find('\n', last_error_pos)
                    error_str = full_error_str[last_error_pos:last_error_pos_2 if last_error_pos_2 - last_error_pos < str_limit else last_error_pos + str_limit]

                    last_function_pos = full_error_str.rfind('| $module:')
                    last_function_pos_2 = full_error_str.find('\n', last_function_pos)
                    function_str = full_error_str[last_function_pos:last_function_pos_2 if last_function_pos_2 - last_function_pos < str_limit else last_function_pos + str_limit]

                    #print(error_str)
                    #item['lfi_errors'].append('{}({})'.format(error_str, function_str))
                    item['lfi_errors'].append('{}'.format(error_str))

            if 'Warnings' in ip.LastFinishInfo:
                for index, warning in ip.LastFinishInfo.Warnings:
                    full_error_str = '{}'.format(warning.Error.ref)
                    last_error_pos = full_error_str.rfind('| error')
                    last_error_pos_2 = full_error_str.find('\n', last_error_pos)
                    error_str = full_error_str[last_error_pos:last_error_pos_2 if last_error_pos_2 - last_error_pos < str_limit else last_error_pos + str_limit]

                    last_function_pos = full_error_str.rfind('| $module:')
                    last_function_pos_2 = full_error_str.find('\n', last_function_pos)
                    function_str = full_error_str[last_function_pos:last_function_pos_2 if last_function_pos_2 - last_function_pos < str_limit else last_function_pos + str_limit]

                    #item['lfi_warnings'].append('{}({})'.format(error_str, function_str))
                    item['lfi_warnings'].append('{}'.format(error_str))

        if 'LastStartInfo' in ip and 'Time' in ip.LastStartInfo:
            item['lsi_time'] =  '{0}'.format(describe_time(ip.LastStartInfo.Time.ref))

        if 'NextBackupTime' in ip and 'Time' in ip.NextBackupTime:
            item['ns_time'] =  '{0}'.format(describe_time(ip.NextBackupTime.Time.ref))

        if 'Centralized' in ip:
            item['cpi'] = '{0}'.format(ip.Centralized.PlanID.ref)

        if 'Local' in ip:
            item['lpi'] = '{0}'.format(ip.Local.PlanID.ref)

        item['legacy_pi'] = '{0}'.format(ip.Plan.ref) if 'Plan' in ip else '-'

        items.append(item)
    return items


def do_deletion(connection):

    print('Do you want to delete MachineManagement::Machine related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id).pattern)
        print('deleted.')
    else:
        print('skipped.')

    if args.instance_id:
        print('Do you want to delete all Gtob::Dto::ItemProtection related to this machine? (y/n)')
        if ask_user():
            connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.InstanceID', args.instance_id).pattern)
            print('deleted.')
        else:
            print('skipped.')

    print('Do you want to delete Msp::AMS::Dto::Machine related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(args.machine_id)).pattern)
        print('deleted.')
    else:
        print('skipped.')


    print('Do you want to delete all InstanceManagement::Instance objects related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::InstanceAspect', '.Key.HostID', args.machine_id).pattern)
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.HostID', args.machine_id).pattern)
        print('deleted.')
    else:
        print('skipped.')


def undeploy_local_plan(connection, pp_info, plan_id):
    try:
        host_id = pp_info[plan_id]['source']

        if not host_id:
            Log.write('Can\'t find host_id for plan {0}'.format(plan_id))
            return

        if 'status' in pp_info[plan_id] and pp_info[plan_id]['status'] != 0:
            Log.write('Can\'t undeploy plan {0} because agent ({1}) is OFFLINE'.format(plan_id, host_id))
            return

        Log.write('Trying to undeploy Gtob::Dto::ProtectionPlan ({0}) from host ({1})...'.format(plan_id, host_id), end='')

        arg = acrort.common.Guid(plan_id)
        if host_id:
            activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg, target_machine=host_id)
        else:
            activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg)

        try:
            if host_id:
                result = connection.tol.get_result(activity_id, target_machine=host_id)
            else:
                result = connection.tol.get_result(activity_id)
        except acrort.Exception as ex:
            if acrort.common.interrupt_sentinel:
                if host_id:
                    connection.tol.cancel_activity(activity_id, target_machine=host_id)
                else:
                    connection.tol.cancel_activity(activity_id)
            Log.write('canceled')
        Log.write('done')

        Log.write('Removing synced Gtob::Dto::ProtectionPlan ({0})...'.format(plan_id), end='')
        connection.dml.delete(pattern=acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', plan_id), host_id).pattern)
        Log.write('done')

        Log.write('Removing Gtob::Dto::ItemProtection from host({0})...'.format(host_id), end='')
        bytes_arg = None
        with open("a_injection.py", "rb") as tol_command:
            bytes_arg = acrort.common.Blob(bytes=tol_command.read())

        run_request = [
            ('.Script.Location', 'string', "attachment:a_injection.py?delete_legacy_item_protections"),
            ('.Script.Argument', 'string', plan_id),
            ('.Script.Body', 'blob', bytes_arg)
        ]
        request_unit = acrort.plain.Unit(flat=run_request)
        activity_id = connection.tol.launch_command(command=acrort.remoting.RUN_SCRIPT_COMMAND_ID_BUSINESS, argument=request_unit, target_machine=host_id)
        result = connection.tol.get_result(activity_id, target_machine=host_id)
        Log.write('done')
    except Exception as e:
        Log.write('Error: {0}'.format(e))


def _init_counters(connection, data_cache, entry_id, spec):
    entry_id_internal = '_{}'.format(entry_id)
    data_cache[entry_id_internal] = {}
    data_cache[entry_id_internal]['wrong'] = 0
    data_cache[entry_id_internal]['ok'] = 0
    data_cache[entry_id_internal]['total'] = acrobind.count_objects_by_spec(connection, spec)
    data_cache[entry_id_internal]['counter_reset'] = data_cache[entry_id_internal]['total'] / 10
    data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter_reset']


def _update_counters_and_file(data_cache, entry_id, filename, wrong_item_id, error_string):
    entry_id_internal = '_{}'.format(entry_id)

    if error_string:
        data_cache[entry_id_internal]['wrong'] = data_cache[entry_id_internal]['wrong'] + 1
        with open(filename, "a") as myfile:
            myfile.write('{0}: {1}\n'.format(wrong_item_id, error_string))
    else:
        data_cache[entry_id_internal]['ok'] = data_cache[entry_id_internal]['ok'] + 1
    data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter'] - 1

    if not data_cache['specific_tenant'] and data_cache[entry_id_internal]['counter'] <= 0:
        Log.write('-', end='')
        data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter_reset']


def _do_check(connection, data_cache, entry_id, filename, spec, callback):
    entry_id_internal = '_{}'.format(entry_id)
    with open(filename, "w") as myfile:
        myfile.truncate()
    if not data_cache['specific_tenant']:
        Log.write('[----------][{}]'.format(data_cache[entry_id_internal]['total']))
        Log.write('[', end='')
    else:
        Log.write('Objects count: {}'.format(data_cache[entry_id_internal]['total']))
    start = time.time()
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    if not data_cache['specific_tenant']:
        Log.write('-]')
    Log.write('OK: {0} WRONG: {1}. Elapsed: {2:.2f} s'.format(data_cache[entry_id_internal]['ok'], data_cache[entry_id_internal]['wrong'], time.time() - start))


def check_tenants_consistency(connection, data_cache):
    data_cache['tenants'] = {}
    spec = acrobind.create_viewspec_by_is('Tenants::HierarchyNode')
    filename = 'amsctl_wrong_tenants.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'tenants', spec)

    def tenants_callback(connection, cache, file, object):
        tenant_id = '{0}'.format(object.ID.ref)

        parent_exists = False
        error_string = ''
        if 'ParentID' in object:
            parent_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_parent_id_field')

        locator_exists = False
        if 'Locator' in object:
            locator_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_locator')

        kind_exists = False
        if 'Kind' in object:
            kind_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_kind')

        data_cache['tenants'][tenant_id] = {}
        data_cache['tenants'][tenant_id]['parent_exists'] = parent_exists
        data_cache['tenants'][tenant_id]['locator_exists'] = locator_exists
        data_cache['tenants'][tenant_id]['kind_exists'] = kind_exists
        data_cache['tenants'][tenant_id]['used'] = False

        _update_counters_and_file(data_cache, 'tenants', file, tenant_id, error_string)

    callback = lambda x: tenants_callback(connection, data_cache, filename, x)
    Log.write('Checking Tenants::HierarchyNode consistency. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'tenants', filename, spec, callback)


def check_orphan_msp_machines(connection, data_cache):
    data_cache['msp_machines'] = {}
    data_cache['to_delete']['msp_machines'] = []
    spec = acrobind.create_viewspec_by_is('Msp::AMS::Dto::Machine')
    filename = 'amsctl_wrong_msp_machines.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Owner', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'msp_machines', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask4('.AgentID', '.IsEnabled', '.OwnerID', '.PublicKey'))

    def msp_machine_callback(connection, cache, file, object):
        host_id_str = '{}'.format(object.AgentID.ref)
        cache['msp_machines'][host_id_str] = {}
        cache['msp_machines'][host_id_str]['used'] = False
        cache['msp_machines'][host_id_str]['public_key_exists'] = False
        cache['msp_machines'][host_id_str]['is_enabled'] = False
        cache['msp_machines'][host_id_str]['is_enabled_field_exists'] = False
        cache['msp_machines'][host_id_str]['owner_id'] = ''
        cache['msp_machines'][host_id_str]['owner_id_field_exists'] = False
        cache['msp_machines'][host_id_str]['owner_exists'] = False

        error_string = ''
        if 'PublicKey' in object:
            cache['msp_machines'][host_id_str]['public_key_exists'] = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_public_key')

        if 'IsEnabled' in object:
            cache['msp_machines'][host_id_str]['is_enabled_field_exists'] = True
            cache['msp_machines'][host_id_str]['is_enabled'] = object.IsEnabled.ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_is_enabled')

        if 'OwnerID' in object:
            cache['msp_machines'][host_id_str]['owner_id_field_exists'] = True
            cache['msp_machines'][host_id_str]['owner_id'] = object['OwnerID'].ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_owner_id')

        if cache['msp_machines'][host_id_str]['owner_id'] in data_cache['tenants']:
            data_cache['tenants'][cache['msp_machines'][host_id_str]['owner_id']]['used'] = True
            cache['msp_machines'][host_id_str]['owner_exists'] = True
        else:
            error_string = '{0} {1}'.format(error_string, 'no_owner(d)')
            data_cache['to_delete']['msp_machines'].append(host_id_str)

        _update_counters_and_file(data_cache, 'msp_machines', file, host_id_str, error_string)

    callback = lambda x: msp_machine_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Msp::Agent::Dto::Machine. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'msp_machines', filename, spec, callback)


def is_mobile_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
            return True
    return False


def is_ad_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{9F148D12-3653-478B-A039-239BE36950AB}':
            return True
    return False


def is_ati_agent(agent_info):
    for index, a in agent_info:
        agent_type_id = a.Id.ref
        if agent_type_id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
            return True
        #Home agent have no Version AgentInfo and have empty Id in AgentInfo
        if len(agent_info) == 1 and agent_type_id == '':
            return True
        else:
            return False
    return False


def is_win_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
            return True
    return False

def is_sql_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
            return True
    return False

def is_esx_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
            return True
    return False

def is_hyperv_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
            return True
    return False

def is_exchange_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{30BAF589-C02A-463F-AB37-5A9193375700}':
            return True
    return False


def check_if_tenant_is_valid(object, data_cache, item_type, item_id):
    tenant_id = '-'
    data_cache[item_type][item_id]['has_tenant'] = False

    error_string = ''
    if 'Tenant' in object:
        tenant_id = object.Tenant.ID.ref
        data_cache[item_type][item_id]['has_tenant'] = True
    else:
        error_string = '{0} {1}'.format(error_string, 'missing_tenant_field')

    data_cache[item_type][item_id]['tenant'] = tenant_id

    if data_cache['resolve_links']:
        data_cache[item_type][item_id]['has_existing_tenant'] = False
        if data_cache[item_type][item_id]['has_tenant']:
            if tenant_id in data_cache['tenants']:
                data_cache[item_type][item_id]['has_existing_tenant'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_tenant(d)')
                data_cache['to_delete'][item_type].append(item_id)
        else:
            data_cache[item_type][item_id]['has_existing_tenant'] = True
    return error_string


def check_orphan_agents(connection, data_cache):
    data_cache['agents'] = {}
    data_cache['to_delete']['agents'] = []
    spec = acrobind.create_viewspec_machines_by_role(0)
    filename = 'amsctl_wrong_agents.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'agents', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask4('.ID', '.Role', '.Tenant', '.Info.Agents'))

    def agents_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['agents'][id_str] = {}
        data_cache['agents'][id_str]['used'] = False
        data_cache['agents'][id_str]['has_existing_msp_machine'] = False

        error_string = ''
        #Mobile agents don't have Msp::Agent::Dto::Configuration
        if not is_mobile_agent(object.Info.Agents):
            if id_str in data_cache['msp_machines']:
                data_cache['agents'][id_str]['has_existing_msp_machine'] = True
                data_cache['msp_machines'][id_str]['used'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_msp_machine')

        tenant_error = check_if_tenant_is_valid(object, data_cache, 'agents', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'agents', file, id_str, error_string)

    callback = lambda x: agents_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan MachineManagement::Machine. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'agents', filename, spec, callback)


def check_orphan_instances(connection, data_cache):
    data_cache['instances'] = {}
    data_cache['to_delete']['instances'] = []
    spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    filename = 'amsctl_wrong_instances.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)
    _init_counters(connection, data_cache, 'instances', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.HostID', '.Tenant'))

    def instance_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['instances'][id_str] = {}
        data_cache['instances'][id_str]['has_host_id'] = False
        data_cache['instances'][id_str]['host_id'] = '-'
        data_cache['instances'][id_str]['has_existing_host'] = False
        data_cache['instances'][id_str]['has_existing_tenant'] = False
        data_cache['instances'][id_str]['has_tenant'] = False
        data_cache['instances'][id_str]['is_deprecated'] = False

        error_string = ''

        if is_deprecated_vm_instance(id_str):
            error_string = '{0} {1}'.format(error_string, 'deprecated_vm_instance(d)')
            data_cache['instances'][id_str]['is_deprecated'] = True
            data_cache['to_delete']['instances'].append(id_str)
        else:
            if 'HostID' in object:
                data_cache['instances'][id_str]['has_host_id'] = False
                data_cache['instances'][id_str]['host_id'] = '{}'.format(object.HostID.ref)
            else:
                error_string = '{0} {1}'.format(error_string, 'missing_host_id_field')

            host_id_str = data_cache['instances'][id_str]['host_id']
            if host_id_str in data_cache['agents']:
                data_cache['instances'][id_str]['has_existing_host'] = True
                data_cache['agents'][host_id_str]['used'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_host')

            tenant_error = check_if_tenant_is_valid(object, data_cache, 'instances', id_str)
            if tenant_error:
                error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'instances', file, id_str, error_string)

    callback = lambda x: instance_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan InstanceManagement::Instance. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'instances', filename, spec, callback)


def check_orphan_plans(connection, data_cache):
    data_cache['plans'] = {}
    data_cache['to_delete']['plans'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan')
    filename = 'amsctl_wrong_plans.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'plans', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Origin', '.Tenant'))

    def plan_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['plans'][id_str] = {}
        data_cache['plans'][id_str]['origin'] = '-'

        error_string = ''

        if 'Origin' in object:
            data_cache['plans'][id_str]['status'] = object.Origin.ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_origin_field')

        tenant_error = check_if_tenant_is_valid(object, data_cache, 'plans', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'plans', file, id_str, error_string)

    callback = lambda x: plan_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::ProtectionPlan. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'plans', filename, spec, callback)


def check_deployment_fact_consistency(connection, data_cache):
    data_cache['deployment_facts'] = {}
    data_cache['to_delete']['deployment_facts'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Protection::PlanDeploymentFact')
    filename = 'amsctl_wrong_deployment_facts.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.PlanObject.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'deployment_facts', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.PlanObject.CentralizedProtectionPlan', '.PlanObject.Tenant'))

    def fact_callback(connection, cache, file, object):
        host_id_str = '{}'.format(object.ID.Host.ref)
        plan_id_str = '{}'.format(object.ID.Plan.ref)
        id_str = '{0}+{1}'.format(host_id_str, plan_id_str)

        data_cache['deployment_facts'][id_str] = {}
        data_cache['deployment_facts'][id_str]['plan'] = plan_id_str
        data_cache['deployment_facts'][id_str]['host'] = host_id_str
        data_cache['deployment_facts'][id_str]['need_redeploy'] = False
        data_cache['deployment_facts'][id_str]['exists'] = True

        source_id = acrobind.get_trait_value('Source', object)

        error_string = ''
        if source_id is None:
            error_string = '{0} {1}'.format(error_string, 'missing_source_trait')

        if source_id is not None and str(source_id) != host_id_str :
            error_string = '{0} {1}'.format(error_string, 'source_differes_from_host')

        if plan_id_str not in data_cache['plans']:
            error_string = '{0} {1}'.format(error_string, 'missing_plan(d)')
            data_cache['to_delete']['deployment_facts'].append(plan_id_str)

        if 'PlanObject' in object:
            tenant_error = check_if_tenant_is_valid(object.PlanObject, data_cache, 'deployment_facts', id_str)
            if tenant_error:
                error_string = '{0} {1}'.format(error_string, tenant_error)
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_plan_object_field')

        _update_counters_and_file(data_cache, 'deployment_facts', file, id_str, error_string)

    callback = lambda x: fact_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Protection::PlanDeploymentFact. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'deployment_facts', filename, spec, callback)


def check_protections_consistency(connection, data_cache):
    if args.parameter1 is not None:
        Log.write('Can\'t calculate protection for specific tenant.')
        #return

    data_cache['protections'] = {}
    data_cache['to_delete']['protections'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Dto::CentralizedProtection')
    filename = 'amsctl_wrong_protections.txt'

    _init_counters(connection, data_cache, 'protections', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask2('.ID', '.AffectedMachines'))

    def protection_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)
        plan_id_str = id_str

        data_cache['protections'][id_str] = {}

        error_string = ''
        if plan_id_str not in data_cache['plans']:
            error_string = '{0} {1}'.format(error_string, 'missing_plan(d)')
            data_cache['to_delete']['protections'].append(id_str)
        else:
            for (number, affected_machine) in object.AffectedMachines:
                fact_id = '{0}+{1}'.format(affected_machine.ref, plan_id_str)

                no_fact = fact_id not in data_cache['deployment_facts']
                if no_fact:
                    error_string = '{0} {1}'.format(error_string, 'missing_deployment_fact')
                    data_cache['deployment_facts'][fact_id] = {}
                    data_cache['deployment_facts'][fact_id]['plan'] = plan_id_str
                    data_cache['deployment_facts'][fact_id]['host'] = affected_machine.ref
                    data_cache['deployment_facts'][fact_id]['exists'] = False

                    remote_spec = acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.CentralizedProtectionPlan', plan_id_str), affected_machine.ref)
                    remote_spec = acrobind.viewspec_apply_mask(remote_spec, acrobind.create_mask2('.ID', '.CentralizedProtectionPlan'))
                    (mms_plan_selected, mms_plan) = acrobind.safe_select_object(connection, remote_spec)
                    if mms_plan_selected:
                        if mms_plan is None:
                            error_string = '{0} {1}'.format(error_string, 'plan_missing_on_agent(r)')
                            data_cache['deployment_facts'][fact_id]['need_redeploy'] = True
                            data_cache['to_redeploy'].append(plan_id_str)
                    else:
                        if mms_plan is None:
                            error_string = '{0} {1}'.format(error_string, 'unknown_plan_state_on_agent')
                            data_cache['deployment_facts'][fact_id]['need_redeploy'] = True
                            data_cache['unknown_to_redeploy'].append(plan_id_str)

        _update_counters_and_file(data_cache, 'protections', file, id_str, error_string)

    callback = lambda x: protection_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::CentralizedProtection. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'protections', filename, spec, callback)


def check_orphan_item_protections(connection, data_cache):
    data_cache['item_protections'] = {}
    data_cache['to_delete']['item_protections'] = []
    data_cache['item_protections']['stat'] = {}

    data_cache['item_protections']['stat']['is_legacy'] = 0
    data_cache['item_protections']['stat']['missing_tenant_field'] = 0
    data_cache['item_protections']['stat']['total'] = 0
    data_cache['item_protections']['stat']['error_item_type'] = 0
    data_cache['item_protections']['stat']['error_item_id'] = 0
    data_cache['item_protections']['stat']['local_item_protection'] = 0
    data_cache['item_protections']['stat']['missing_centralized'] = 0
    data_cache['item_protections']['stat']['missing_local'] = 0
    data_cache['item_protections']['stat']['missing_item_type'] = 0
    data_cache['item_protections']['stat']['missing_item_id'] = 0

    spec = acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection')
    filename = 'amsctl_wrong_item_protections.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'item_protections', spec)

    def ip_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)
        is_centralized = [unit.ref for name, unit in object.traits if unit.ref == "Gtob::Dto::Centralized::ItemProtection"]

        stat = data_cache['item_protections']['stat']
        data_cache['item_protections'][id_str] = {}
        data_cache['item_protections'][id_str]['cplan'] = '-'
        data_cache['item_protections'][id_str]['lplan'] = '-'

        error_string = ''

        stat['total'] = stat['total'] + 1

        is_legacy = False
        if 'Instance' in object:
            is_legacy = True
            stat['is_legacy'] = stat['is_legacy'] + 1

        tenant_id = ''
        tenant_error = check_if_tenant_is_valid(object, data_cache, 'item_protections', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        has_centralized = False
        if 'Centralized' in object:
            has_centralized = True

        has_local = False
        if 'Local' in object:
            has_local = True

        local_item_protection = False
        if 'LastStartInfo' in object and 'BackupFrame' in object.LastStartInfo:
            start_frame = object.LastStartInfo.BackupFrame.ref
            if start_frame.startswith('LOCAL'):
                local_item_protection = not is_centralized
                if local_item_protection:
                    stat['local_item_protection'] = stat['local_item_protection'] + 1

        item_type = get_optional(object, 'ItemType')
        wrong_item_type = False
        if 'ItemType' in object and item_type == 0:
            stat['error_item_type'] = stat['error_item_type']  + 1
            error_string = '{0} {1}'.format(error_string, 'error_item_type')

        item_id = get_optional(object, 'ItemID')

        if 'ItemID' in object and str(item_id) == '00000000-0000-0000-0000-000000000000':
            stat['error_item_id'] = stat['error_item_id']  + 1
            error_string = '{0} {1}'.format(error_string, 'error_item_id')
            data_cache['item_protections'][id_str]['wrong_item_id'] = True

        if cache['resolve_links']:
            if object is not None and 'Centralized' in object and 'PlanID' in object.Centralized:
                cplan_id = '{0}'.format(object.Centralized.PlanID.ref)
                data_cache['item_protections'][id_str]['cplan'] = cplan_id
                if cplan_id not in data_cache['plans']:
                    error_string = '{0} {1}'.format(error_string, 'link_to_missing_centralized_plan')

            if object is not None and 'Local' in object and 'PlanID' in object.Local:
                lplan_id = '{0}'.format(object.Local.PlanID.ref)
                data_cache['item_protections'][id_str]['lplan'] = lplan_id
                if lplan_id not in data_cache['plans']:
                    error_string = '{0} {1}'.format(error_string, 'link_to_missing_local_plan')

        if not is_legacy:
            if not has_local:
                any_problem_exists = True
                stat['missing_local'] = stat['missing_local'] + 1
                error_string = '{0} {1}'.format(error_string, 'missing_local')

            if not has_centralized and is_centralized:
                any_problem_exists = True
                stat['missing_centralized'] = stat['missing_centralized'] + 1
                error_string = '{0} {1}'.format(error_string, 'missing_centralized')

            if not local_item_protection and (item_type or item_id):
                if item_type is None:
                    stat['missing_item_type'] = stat['missing_item_type'] + 1
                    error_string = '{0} {1}'.format(error_string, 'missing_item_type')
                    any_problem_exists = True

                if item_id is None:
                    stat['missing_item_id'] = stat['missing_item_id'] + 1
                    error_string = '{0} {1}'.format(error_string, 'missing_item_id')
                    any_problem_exists = True

        #log_str = '{0}: {1}'.format(id_str, error_string)
        #if not cache['resolve_links']:
        #    log_str = '{0}\n{1}'.format(error_string, object)
        log_str = '{0}\n{1}'.format(error_string, object)
        _update_counters_and_file(data_cache, 'item_protections', file, id_str, error_string)

    callback = lambda x: ip_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::ItemProtection. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'item_protections', filename, spec, callback)
    stat = data_cache['item_protections']['stat']
    for key in sorted(stat):
        Log.write('{0}: {1}'.format(key, stat[key]))


def _log_for_unused_objects(connection, cache, entry_id, filename):
    with open(filename, "w") as myfile:
        myfile.truncate()

    counter = 0
    for id, value in cache[entry_id].items():
        if not value['used']:
            counter = counter + 1
            with open(filename, "a") as myfile:
                myfile.write('{}\n'.format(id))
    Log.write('Unused {}: {}'.format(entry_id, counter))


def check_for_unused_objects(connection, cache):
    _log_for_unused_objects(connection, cache, 'tenants', 'amsctl_unused_tenants.txt')
    _log_for_unused_objects(connection, cache, 'msp_machines', 'amsctl_unused_msp_machines.txt')
    _log_for_unused_objects(connection, cache, 'agents', 'amsctl_unused_agents.txt')


def consistency_redeploy_plans(connection, ids):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    log_file_name = 'amsctl_redeploy_{}.txt'.format(creation_time)
    Log.write('Plans to redeploy: {0}'.format(len(ids)))

    start = time.time()

    for plan_id in ids:
        Log.write('Redeploying \'{0}\''.format(plan_id))
        try:
            redeploy_plan(connection, plan_id)
        except Exception as error:
            Log.write('Skipping because of error: {0}'.format(error))

    Log.write('elapsed: {0:.2f} s.'.format(time.time() - start))

def delete_obsolete_data(connection, cache):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    log_file_name = 'amsctl_deleted_objects_{}.txt'.format(creation_time)
    Log.write('Removing wrong objects...')

    start = time.time()
    def handle_ids(connection, spec, filename):

        def store_object(object, filename):
            with open(filename, "a") as myfile:
                myfile.write('{}\n'.format(object))

        callback = lambda x: store_object(x, filename)
        acrobind.enumerate_objects(connection, spec, callback, error_log)
        connection.dml.delete(pattern=spec.pattern)

    for type, ids in cache['to_delete'].items():
        Log.write('To delete \'{0}\': {1}'.format(type, len(ids)))

        if len(ids) <= 0:
            continue
        if type == 'agents':
            spec = acrobind.create_viewspec_by_is('MachineManagement::Machine')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)

        if type == 'instances':
            spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'deployment_facts':
            spec = acrobind.create_viewspec_by_is('Gtob::Protection::PlanDeploymentFact')
            ids_pattern = []
            for id in ids:
                ids_pattern.append([('', 'guid', id)])

            pattern = [
                ('.ID.Plan', 'guid', '00000000-0000-0000-0000-000000000000'),
                ('.ID.Plan^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
            ]
            spec = acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), spec.options)
            handle_ids(connection, spec, log_file_name)
        if type == 'plans':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'item_protections':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'protections':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::CentralizedProtection')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'msp_machines':
            spec = acrobind.create_viewspec_by_is('Msp::AMS::Dto::Machine')

            ids_pattern = []
            for id in ids:
                ids_pattern.append([('', 'string', id)])

            pattern = [
                ('.AgentID', 'string', '00000000-0000-0000-0000-000000000000'),
                ('.AgentID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
            ]
            spec = acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), spec.options)
            handle_ids(connection, spec, log_file_name)

    Log.write('elapsed: {0:.2f} s.'.format(time.time() - start))


def obsolete_data(connection):

    cache = {}
    cache['resolve_links'] = True
    cache['specific_tenant'] = False
    cache['to_delete'] = {}
    cache['to_redeploy'] = []
    cache['unknown_to_redeploy'] = []
    action = 'all'

    if args.parameter2 is not None and args.parameter2.startswith('ip'):
        action = 'item_protection_only'
        cache['resolve_links'] = False
    elif args.parameter2 is not None and args.parameter2.startswith('protection'):
        action = 'protection_plan_only'
        cache['resolve_links'] = False

    if args.parameter1 is not None:
        cache['specific_tenant'] = True

    Log.write('Action: {}'.format(action))
    Log.write('Resolve links between objects: {}'.format(cache['resolve_links']))

    if action in ['all']:
        check_tenants_consistency(connection, cache)
        check_orphan_msp_machines(connection, cache)
        check_orphan_agents(connection, cache)
        check_orphan_instances(connection, cache)

    if action in ['protection_plan_only', 'all']:
        check_orphan_plans(connection, cache)
        check_deployment_fact_consistency(connection, cache)
        check_protections_consistency(connection, cache)
    if action in ['item_protection_only', 'all']:
        check_orphan_item_protections(connection, cache)

    if action in ['all']:
        check_for_unused_objects(connection, cache)

    for type, ids in cache['to_delete'].items():
        Log.write('To delete \'{0}\': {1}'.format(type, len(ids)))
    Log.write('To redeploy: {0}'.format(len(cache['to_redeploy'])))
    Log.write('Unknown deployment status: {0}'.format(len(cache['unknown_to_redeploy'])))

    if args.fix:
        print('Do you want to redeploy wrong protection plans {0}? (y/n)'.format(len(cache['to_redeploy'])))
        if ask_user():
            consistency_redeploy_plans(connection, cache['to_redeploy'])
        print('Do you want to redeploy unknown protection plans {0}? (y/n)'.format(len(cache['unknown_to_redeploy'])))
        if ask_user():
            consistency_redeploy_plans(connection, cache['unknown_to_redeploy'])
    if args.delete:
        print('Do you want to delete obsolete data? (y/n)')
        if ask_user():
            delete_obsolete_data(connection, cache)


def init_agent_stat(stat):
    stat['by_version'] = {}
    stat['by_type'] = {}
    stat['by_type']['mac'] = {}
    stat['by_type']['lin'] = {}
    stat['by_type']['win'] = {}
    stat['by_type']['exc'] = {}
    stat['by_type']['vmw'] = {}
    stat['by_type']['mob'] = {}
    stat['by_type']['sql'] = {}
    stat['by_type']['oth'] = {}


def map_agent_type(id):
    if id == '':
        return 'Version'

    if id == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
        return 'Agent for Windows'

    if id == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
        return 'Agent for SQL'

    if id == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
        return 'Agent for Mobile'

    if id == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
        return 'Agent for VMware'

    if id == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
        return 'Agent for Hyper-V'

    if id == '{30BAF589-C02A-463F-AB37-5A9193375700}':
        return 'Agent for Exchange'

    if id == '{CDC40814-3769-4D13-B222-629463D3F5CA}':
        return 'Agent for ESX (Appliance)'

    if id == '{09726067-5E56-4775-8D3B-FD9110BCAAD1}':
        return 'Agent for ARX single pass'

    if id == '{383CD411-7A5D-4658-B184-3B651A6E12BB}':
        return 'Agent for Online exchange agent'

    if id == '{9F148D12-3653-478B-A039-239BE36950AB}':
        return 'Agent for AD'

    if id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
        return 'Agent for Home'

    return 'oth'


def increment_counter(stat, counter_name):
    if counter_name not in stat:
        stat[counter_name] = 1
        return
    stat[counter_name] += 1


def init_agents_info(data):
    data[map_agent_type('')] = ''
    data[map_agent_type('{BA262882-C484-479A-8D07-B0EAC55CE27B}')] = ''
    data[map_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')] = ''
    data[map_agent_type('{EC3CF038-C08A-4341-82DF-F75133705F63}')] = ''
    data[map_agent_type('{87671A98-2B47-4D4C-98FB-490CA111E7A7}')] = ''
    data[map_agent_type('{C293F7DD-9531-4916-9346-12B0F3BFCCAA}')] = ''
    data[map_agent_type('{30BAF589-C02A-463F-AB37-5A9193375700}')] = ''
    data[map_agent_type('{CDC40814-3769-4D13-B222-629463D3F5CA}')] = ''
    data[map_agent_type('{09726067-5E56-4775-8D3B-FD9110BCAAD1}')] = ''
    data[map_agent_type('{383CD411-7A5D-4658-B184-3B651A6E12BB}')] = ''
    data[map_agent_type('{9F148D12-3653-478B-A039-239BE36950AB}')] = ''
    #home
    data[map_agent_type('{8397FC51-C78E-4E26-9860-6A4A8622DF7C}')] = ''

def map_os_type(type):
    if type == 1:
        return 'Unknown'

    if type == 2:
        return 'Windows9x'

    if type == 3:
        return 'Windows'

    if type == 4:
        return 'Linux'

    if type == 5:
        return 'MacOs'

    return '{0}'.format(value)


def map_os_caps(value):
    result = []
    if value & 1:
        result.append('BOOTMEDIA')

    if value & 2:
        result.append('SERVER')

    if value & 4:
        result.append('APPLIANCE')

    if value & 8:
        result.append('LINUX_RAMDISK')

    if value & 16:
        result.append('HYPERV')

    if value & 32:
        result.append('DR_APPLIANCE')

    return '|'.join(result)


def map_architecture_edition(value):
    if value == 0:
        return 'UNKNOWN'

    if value == 1:
        return 'X86'

    if value == 2:
        return 'X64'

    return '{0}'.format(value)


def get_app_stat(info, host_id):
    if host_id not in info['apps']:
        info['apps'][host_id] = {}
        info['apps'][host_id]['exchange'] = {}
        info['apps'][host_id]['exchange']['2003'] = 0
        info['apps'][host_id]['exchange']['2007'] = 0
        info['apps'][host_id]['exchange']['2010'] = 0
        info['apps'][host_id]['exchange']['2013'] = 0
        info['apps'][host_id]['exchange']['-'] = 0
        info['apps'][host_id]['mssql'] = {}
        info['apps'][host_id]['mssql']['SQL Server 2005'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2008'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2008R2'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2012'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2014'] = 0
        info['apps'][host_id]['mssql']['SQL Server Unknown'] = 0

    return info['apps'][host_id]


def map_sql_server_version(version):
    if version.startswith('9.0'):
        return 'SQL Server 2005'
    if version.startswith('10.0'):
        return 'SQL Server 2008'
    if version.startswith('10.50'):
        return 'SQL Server 2008R2'
    if version.startswith('11.0'):
        return 'SQL Server 2012'
    if version.startswith('12.0'):
        return 'SQL Server 2014'
    return 'SQL Server Unknown'


def map_exchange_server_version(version):
    if version == '-':
        return 'Exchange Server Unknown'
    return 'Exchange Server {0}'.format(version)


def collect_summary_info(connection, info, object):
    is_online = (object.Status.ref == 0)
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    info["total"]['total'] = info["total"]['total'] + 1
    if is_online:
        info["total"]['total_online'] = info["total"]['total_online'] + 1

    if is_mobile:
        info["total"]['mobile'] = info["total"]['mobile'] + 1
        if is_online:
            info["total"]['mobile_online'] = info["total"]['mobile_online'] + 1
    elif is_ati:
        info["total"]['home'] = info["total"]['home'] + 1
        if is_online:
            info["total"]['home_online'] = info["total"]['home_online'] + 1
    else:
        info['total']['abr'] = info['total']['abr'] + 1
        if is_online:
            info["total"]['abr_online'] = info["total"]['abr_online'] + 1


def collect_online_info(connection, info, object):
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    info["total"]['total'] = info["total"]['total'] + 1

    if is_mobile:
        info["total"]['mobile'] = info["total"]['mobile'] + 1
    elif is_ati:
        info["total"]['home'] = info["total"]['home'] + 1
    else:
        info['total']['abr'] = info['total']['abr'] + 1


def process_agents(connection, info, object):
    agent_id = '{0}'.format(object.ID.ref)
    info[agent_id] = {}
    data = info[agent_id]

    tenant = ''
    if 'Tenant' in object:
        tenant = object.Tenant.Name.ref
    data['Tenant'] = tenant
    init_agents_info(data)

    is_online = (object.Status.ref == 0)
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    def get_version(agents_info):
        for index, a in object.Info.Agents:
            version = a.Version.ref
            return version

    if is_mobile:
        agent_type = map_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')
        data[agent_type] = get_version(object.Info.Agents)
    elif is_ati:
        agent_type = map_agent_type('{8397FC51-C78E-4E26-9860-6A4A8622DF7C}')
        data[agent_type] = get_version(object.Info.Agents)
    else:
        for index, a in object.Info.Agents:
            version = a.Version.ref
            agent_type = map_agent_type(a.Id.ref)
            data[agent_type] = version

            if agent_type == 'oth':
                Log.write('UnknownType: {0}, {1}'.format(a.Name.ref, a.Id.ref))

    data['ProcessorArchitecture'] = object.Info.Architecture.ref
    #if 'Hardware' in object.Info:
    #    data['MemorySize'] = object.Info.Hardware.MemorySize.ref
    #    data['ProcessorFrequency'] = object.Info.Hardware.ProcessorFrequency.ref
    #    data['ProcessorName'] = object.Info.Hardware.ProcessorName.ref
    #else: #For Mobile
    #    data['MemorySize'] = '-'
    #    data['ProcessorFrequency'] = '-'
    #    data['ProcessorName'] = ''

    #data['Name'] = object.Info.Name.ref
    data['Status'] = object.Status.ref
    data['OSArchitectureEdition'] = map_architecture_edition(object.Info.OS.ArchitectureEdition.ref)
    data['OSName'] = object.Info.OS.Name.ref
    data['OSCaps'] = map_os_caps(object.Info.OS.OSCaps.ref)
    data['OSType'] = map_os_type(object.Info.OS.OSType.ref)
    data['OSVersionMajor'] = object.Info.OS.VersionMajor.ref
    data['OSVersionMinor'] = object.Info.OS.VersionMinor.ref
    if 'TimezoneOffsetInMinutes' in object.Info:
        data['TimezoneOffsetInMinutes'] = object.Info.TimezoneOffsetInMinutes.ref
    else: #For Mobile
        data['TimezoneOffsetInMinutes'] = '-'
    data['LastConnectionTime'] = '-'
    if 'LastConnectionTime' in object:
        data['LastConnectionTime'] = object.LastConnectionTime.ref

    if 'Tenant' in object:
        data['TenantName'] = object.Tenant.Name.ref
        data['TenantID'] = object.Tenant.ID.ref
        data['TenantLocator'] = object.Tenant.Locator.ref
    else:
        data['TenantName'] = '-'
        data['TenantID'] = '-'
        data['TenantLocator'] = '-'
    if 'UpdateState' in object:
        data['UpdateIsAvailable'] = object.UpdateState.UpdateVersion.ref
    else: #For Mobile
        data['UpdateIsAvailable'] = '-'

    app_stat = get_app_stat(info, agent_id)
    for app_version, count in app_stat['mssql'].items():
        data[app_version] = count
    for app_vesion, count in app_stat['exchange'].items():
        data[map_exchange_server_version(app_version)] = count


def print_agent_stat(stat):
    Log.write('By version:')
    sorted_items = sorted(stat['by_version'].items(), key=operator.itemgetter(1))
    sorted_items.reverse()
    for version, count in sorted_items:
        Log.write('\t{0}: {1}'.format(version, count))

    Log.write('By type:')
    for type, data in stat['by_type'].items():
        Log.write('\t{0}:'.format(type))
        sorted_items = sorted(data.items(), key=operator.itemgetter(1))
        sorted_items.reverse()
        for version, count in sorted_items:
            Log.write('\t\t{0}: {1}'.format(version, count))


def process_mssql_instances(connection, info, object):
    host_id = '{0}'.format(object.HostID.ref)
    stat = get_app_stat(info, host_id)
    if 'SqlServerVersion' not in object.Parameters:
        stat['mssql'][map_sql_server_version('-')] += 1
        return
    for index, value in object.Parameters.SqlServerVersion:
        #Log.write('Found mssql version \'{0}\''.format(value.ref))
        app_version = map_sql_server_version(value.ref)
        if app_version not in stat['mssql']:
            stat['mssql'][map_sql_server_version('-')] += 1
        else:
            stat['mssql'][app_version] += 1


def process_exchange_instances(connection, info, object):
    host_id = '{0}'.format(object.HostID.ref)
    stat = get_app_stat(info, host_id)
    #print(object)
    if 'ExchangeServerVersion' not in object.Parameters:
        stat['exchange']['-'] += 1
        return
    for index, value in object.Parameters.ExchangeServerVersion:
        #Log.write('Found exchange version \'{0}\''.format(value.ref))
        if value.ref not in stat['exchange']:
            stat['exchange']['-'] += 1
        else:
            stat['exchange'][value.ref] += 1


def collect_agents_statistics(connection):

    info = {}
    info['total'] = {}
    info['total']['home'] = 0
    info['total']['mobile'] = 0
    info['total']['abr'] = 0
    info['total']['total'] = 0
    info['apps'] = {}

    if args.parameter1 != 'summary' and args.parameter1 != 'online':
        #prefetch application instance info
        Log.write('Collecting information applications...')
        start = time.time()
        callback = lambda x: process_exchange_instances(connection, info, x)
        #INSTANCE_TYPE_EXCHANGE = 6,
        pattern = [
            ('^Is', 'string', 'InstanceManagement::Instance'),
            ('.Type', 'dword', 6),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        acrobind.enumerate_objects(connection, spec, callback, error_log)

        callback = lambda x: process_mssql_instances(connection, info, x)
        #INSTANCE_TYPE_MSSQL = 19,
        pattern = [
            ('^Is', 'string', 'InstanceManagement::Instance'),
            ('.Type', 'dword', 19),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        acrobind.enumerate_objects(connection, spec, callback, error_log)
        Log.write('done ({0:.2f} s)'.format(time.time() - start))

    #determine all agents type
    Log.write('Collecting information about agents...')
    start = time.time()
    spec = acrobind.create_viewspec_machines_by_role(0)

    if args.parameter1 == 'summary':
        info['total']['home_online'] = 0
        info['total']['mobile_online'] = 0
        info['total']['abr_online'] = 0
        info['total']['total_online'] = 0
        spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Status', '.Info'))
        agents_callback = lambda x: collect_summary_info(connection, info, x)
    elif args.parameter1 == 'online':
        pattern = [
            ('^Is', 'string', 'MachineManagement::Machine'),
            ('.Info.Role', 'dword', 0),
            ('.Status', 'dword', 0),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Status', '.Info'))
        agents_callback = lambda x: collect_online_info(connection, info, x)
    else:
        info['total']['home_online'] = 0
        info['total']['mobile_online'] = 0
        info['total']['abr_online'] = 0
        info['total']['total_online'] = 0
        agents_callback = lambda x: process_agents(connection, info, x)

    acrobind.enumerate_objects(connection, spec, agents_callback, error_log)
    Log.write('done ({0:.2f} s)'.format(time.time() - start))

    if args.parameter1 == 'summary' or args.parameter1 == 'online':
        print(info['total'])
        return

    info.pop('apps', None)
    info.pop('total', None)
    print(json.dumps(info))
    #print_agent_stat(info)

    #for tenant, data in info['by_tenant'].items():
    #    Log.write("Tenant: {0}".format(tenant))
    #    print_agent_stat(data)


def map_usert_agent_type(id):

    if id == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
        return 'Workstation'

    if id == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
        return 'SQL'

    if id == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
        return 'Mobile'

    if id == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
        return 'VMware'

    if id == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
        return 'Hyper-V'

    if id == '{30BAF589-C02A-463F-AB37-5A9193375700}':
        return 'Exchange'

    if id == '{CDC40814-3769-4D13-B222-629463D3F5CA}':
        return 'ESX (Appliance)'

    if id == '{09726067-5E56-4775-8D3B-FD9110BCAAD1}':
        return 'ARX single pass'

    if id == '{383CD411-7A5D-4658-B184-3B651A6E12BB}':
        return 'Online ARX'

    if id == '{9F148D12-3653-478B-A039-239BE36950AB}':
        return 'AD'

    if id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
        return 'Home'

    if id == 'home':
        return 'Home'

    return 'unknown'

def init_users_info(data):
    data[map_usert_agent_type('{BA262882-C484-479A-8D07-B0EAC55CE27B}')] = {}
    data[map_usert_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')] = {}
    data[map_usert_agent_type('{EC3CF038-C08A-4341-82DF-F75133705F63}')] = {}
    data[map_usert_agent_type('{87671A98-2B47-4D4C-98FB-490CA111E7A7}')] = {}
    data[map_usert_agent_type('{C293F7DD-9531-4916-9346-12B0F3BFCCAA}')] = {}
    data[map_usert_agent_type('{30BAF589-C02A-463F-AB37-5A9193375700}')] = {}
    data[map_usert_agent_type('{CDC40814-3769-4D13-B222-629463D3F5CA}')] = {}
    data[map_usert_agent_type('{09726067-5E56-4775-8D3B-FD9110BCAAD1}')] = {}
    data[map_usert_agent_type('{383CD411-7A5D-4658-B184-3B651A6E12BB}')] = {}
    data[map_usert_agent_type('{9F148D12-3653-478B-A039-239BE36950AB}')] = {}
    data[map_usert_agent_type('home')] = {}
    data[map_usert_agent_type('unknown')] = {}


def process_users(connection, info, object):
    #print(object)
    #print(len(object.Info.Agents))

    if 'Tenant' not in object:
        #Log.write("Object without tenant info:\n{0}".format(object))
        return

    tenant_id = object.Tenant.ID.ref

    for index, a in object.Info.Agents:
        data = None
        agent_type = None
        #Home agent have no Version AgentInfo and have empty Id in AgentInfo
        if len(object.Info.Agents) == 1 and a.Id.ref == '':
            agent_type = map_usert_agent_type('home')
            #print('Home detected {0}'.format(object))
        elif a.Id.ref:
            agent_type = map_usert_agent_type(a.Id.ref)

        if not agent_type:
            continue

        data = info[agent_type]
        if data is None:
            Log.write('UnknownType: {0}, {1}'.format(a.Name.ref, a.Id.ref))
            print(agent_type)
            print(info)
            continue

        if tenant_id in data:
            #Log.write('Already calculated')
            return

        #Log.write(agent_type)
        data[tenant_id] = {}
        data[tenant_id]["ID"] = object.Tenant.ID.ref
        data[tenant_id]["Name"] = object.Tenant.Name.ref
        data[tenant_id]["Locator"] = object.Tenant.Locator.ref
        data[tenant_id]["ParentID"] = object.Tenant.ParentID.ref
        data[tenant_id]["Kind"] = object.Tenant.Kind.ref


def collect_users_info(connection):

    info = {}
    init_users_info(info)

    #determine all agents type
    Log.write('Collecting information about agents...')
    start = time.time()
    agents_callback = lambda x: process_users(connection, info, x)
    spec = acrobind.create_viewspec_machines_by_role(0)
    #spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Tenant', '.Info.Agents', '.Info.OS'))
    acrobind.enumerate_objects(connection, spec, agents_callback, error_log)
    Log.write('done ({0:.2f} s)'.format(time.time() - start))
    print(json.dumps(info))


def process_autoupdate(connection, object, table):
    if args.extra:
        Log.write(object)

    if table:
        table.add_row(['{}'.format(object.Version.ref), '{}'.format(object.OS.ref), '{}'.format(object.Arch.ref), '{}'.format(object.Locale.ref), '{}'.format(object.BuildUrl.ref)])

    if args.fix:
        print('Do you want to delete this object(y/n)?')
        if ask_user():
            object_pattern = [
                ('^Is', 'string', 'AutoUpdate::Dto::Update'),
                ('.BuildUrl', 'string', object.BuildUrl.ref),
                ('.ID.ID', 'guid', object.ID.ID.ref),
                ('.ID.SequencedID', 'qword', object.ID.SequencedID.ref),
            ]
            connection.dml.delete(pattern=acrort.plain.Unit(flat=object_pattern))
            print('deleted.')
        else:
            print('skipped.')


def analyze_updates(connection):
    build_url = None
    if args.build_url:
        build_url = args.build_url

    update_id = None
    if args.update_id:
        update_id = args.update_id


    Log.write('Listing updates for build url {0} and update id {1}'.format(build_url if build_url else "All", update_id if update_id else "All"))

    table = prettytable.PrettyTable(["Version", "OS", "Arch", "Locale", "BuildUrl"])
    table.align["Version"] = "l"
    table.padding_width = 1
    callback = lambda x: process_autoupdate(connection, x, table)
    pattern = [
        ('^Is', 'string', 'AutoUpdate::Dto::Update'),
    ]
    if build_url:
        pattern.append(('.BuildUrl', 'string', build_url))
        pattern.append(('.BuildUrl^Like', 'string', '%{0}%'.format(build_url)))

    if update_id:
        pass
        #pattern.append(('.ID.ID', 'guid', update_id))

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    print(table.get_string(sortby="Version"))
    print('')


def counter_mode(connection):
    statistics_views = {'machine-statistics': 'Statistics::Agents', 'backup-statistics': 'Statistics::Resources'}
    if args.count in statistics_views:
        stat = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property(statistics_views[args.count], '.Tenant.ID', "all"))
        if not stat:
            Log.write("Failed to select statistics object.")
            return
        print(stat)
    else:
        Log.write("Trying to count objects: {}".format(args.count))
        count = acrobind.count_objects(connection, args.count)
        print("{0}: {1}".format(args.count, count))

#BEGIN --perf_stat analyze--
def get_perf_stat_folder():
    return os.path.join(acrort.fs.APPDATA_COMMON, acrort.common.BRAND_NAME, 'AMS', 'perf_stats')


def init_dml_perf_stat(stat, time_range):
    stat[time_range] = {}
    stat[time_range]['report_count_{}'.format(time_range)] = {}
    stat[time_range]['report_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_create_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_create_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_delete_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_delete_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_update_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_update_time_{}'.format(time_range)] = {}


def load_dml_stat(file_name, stat):
    with open(file_name, 'r') as csvfile:
        try:
            reader = csv.reader(csvfile, 'ams')
            for row in reader:
                stat[row[0]] = int(row[1])
        except Exception as error:
            Log.write('Failed to process stat file \'{0}\', reason: {1}.'.format(file_name, error))


def perf_stat():
    Log.write('DML perf stat')
    dml_perf_stat()


def dml_perf_stat():
    csv.register_dialect('ams', delimiter=';', skipinitialspace=True, quoting=csv.QUOTE_NONE)
    perf_folder = get_perf_stat_folder()
    stat = {}

    desc = {}
    init_dml_perf_stat(stat, 'total')
    init_dml_perf_stat(stat, '1_minute')

    for i in range(1, 10):
        core_id = '0_{}'.format(i)
        desc_file = '{0}/{1}_desc.log'.format(perf_folder, core_id)
        try:
            with open(desc_file, 'r') as csvfile:
                try:
                    reader = csv.reader(csvfile, 'ams')
                    for row in reader:
                        desc[row[0]] = '{}:{}'.format(row[2], row[1])
                except Exception as error:
                    Log.write('Failed to process file \'{0}\', reason: {1}.'.format(desc_file, error))

            for key, value in stat.items():
                for stat_id in value:
                    stat_file_name = '{0}/{1}_{2}.log'.format(perf_folder, stat_id, core_id)
                    load_dml_stat(stat_file_name, stat[key][stat_id])
        except Exception as error:
            Log.write('Failed to process file \'{0}\', reason: {1}. Skipping'.format(desc_file, error))


    for key, stat_pack in stat.items():
        table = prettytable.PrettyTable([key, "value", "file"])

        table.align[key] = "l"
        table.align["value"] = "l"
        table.align["file"] = "l"
        table.padding_width = 1

        last_minute_filter = 100
        total_minute_filter = 1000
        if args.parameter1 is not None:
            last_minute_filter = int(args.parameter1)

        if args.parameter2 is not None:
            total_minute_filter = int(args.parameter2)
        filter = last_minute_filter if key == '1_minute' else total_minute_filter
        for stat_id, state_value in stat_pack.items():
            sorted_stat = reversed(sorted(state_value.items(), key=lambda x:x[1]))
            top_str = ''
            column_name = stat_id
            for i, v in sorted_stat:
                if v > filter:
                    table.add_row([column_name, v, desc[i]])
                    column_name = ''

        Log.write(table)

#END --perf_stat analyze--


def dml_stat():
    Log.write('DML perf stat')
    csv.register_dialect('ams', delimiter=';', skipinitialspace=True, quoting=csv.QUOTE_NONE)
    perf_folder = get_perf_stat_folder()
    stat = {}
    stat['recent'] = {}
    stat['total'] = {}

    stat['result'] = {}
    stat['result']['recent'] = {}
    stat['result']['total'] = {}
    try:
        with open('dml_perf_last_1_minute.log', 'r') as csvfile:
            try:
                reader = csv.reader(csvfile, 'ams')
                for row in reader:
                    stat['recent'][int(row[0])] =  int(row[1])
            except Exception as error:
                Log.write('Failed to process file \'{0}\', reason: {1}.'.format('dml_perf_last_1_minute.log', error))

        with open('dml_perf_total.log', 'r') as csvfile:
            try:
                reader = csv.reader(csvfile, 'ams')
                for row in reader:
                    stat['total'][int(row[0])] =  int(row[1])
            except Exception as error:
                Log.write('Failed to process file \'{0}\', reason: {1}.'.format('dml_perf_total.log', error))
    except Exception as error:
        Log.write('Failed to process file, reason: {0}. Skipping'.format(error))

    def load_stat(stat, type):
        for key, value in stat[type].items():
            temp = key
            counter_id = temp % 100
            temp = temp // 100

            op_id = temp % 100
            temp = temp // 100

            core_id = temp % 100
            temp = temp // 100

            dispatcher_id = temp % 100
            counter_class = temp // 100

            if dispatcher_id not in stat['result'][type]:
                stat['result'][type][dispatcher_id] = {}
            dispatcher_stat = stat['result'][type][dispatcher_id]

            if counter_class > 1:
                core_id = core_id - 1

            if core_id not in dispatcher_stat:
                dispatcher_stat[core_id] = {}
            core_stat = dispatcher_stat[core_id]

            unified_key = key

            if counter_class > 1:
                unified_key = key % 10000000
                unified_key = 100000000 + dispatcher_id * 1000000 + core_id * 10000 + op_id * 100 + counter_id
                #continue
            #if key == 100000001:
            #    print(unified_key)

            if op_id not in core_stat:
                core_stat[op_id] = {}
                def init_op_stat(stat, op_id):
                    stat[op_id] = {}
                    stat[op_id]['key'] = unified_key
                    stat[op_id]['val'] = 0

                init_op_stat(core_stat[op_id], 0)
                init_op_stat(core_stat[op_id], 1)
                init_op_stat(core_stat[op_id], 2)
                init_op_stat(core_stat[op_id], 5)
                init_op_stat(core_stat[op_id], 6)

            op_stat = core_stat[op_id]

            c_id = (counter_class - 1)* 100 + counter_id
            if c_id not in op_stat:
                op_stat[c_id] = {}
                op_stat[c_id]['key'] = {}
                op_stat[c_id]['val'] = {}
            op_stat[c_id]['val'] = value
            op_stat[c_id]['key'] = unified_key

            #Log.write('{} - {}:{}:{}:{}'.format(key, dispatcher_id, core_id, op_id, counter_id))

    load_stat(stat, 'recent')
    load_stat(stat, 'total')

    table = prettytable.PrettyTable(['id', 'core + op', "calls=sucess+failed (time)", "calls=sucess+failed (time)", "avg_rate/cur_rate", "pending", "batch+sql+completion", "batch(elems)/register"])
    for dispatcher, dispatcher_stat in stat['result']['total'].items():
        for core_id, core_stat in dispatcher_stat.items():
            for op_id, op_stat in core_stat.items():

                op_name = ''
                if op_id == 0:
                    op_name = 'create'
                elif op_id == 1:
                    op_name = 'update'
                elif op_id == 2:
                    op_name = 'delete'
                elif op_id == 3:
                    op_name = 'tr'
                elif op_id == 4:
                    op_name = 'tu'
                elif op_id == 5:
                    op_name = 'report'
                elif op_id == 6:
                    op_name = 'subscribe'

                time_val = 0
                calls_val = 0
                s_calls_val = 0
                f_calls_val = 0
                p_calls_val = 0
                b_processing_time = '-'
                sql_exec_time = '-'
                completion_exec_time = '-'
                batch_count = '-'
                batch_elements = '-'
                register_counts = '-'

                key = 0
                for c_id, c_stat in op_stat.items():
                    key = c_stat['key']
                    if c_id == 0:
                        time_val = c_stat['val']
                    elif c_id == 1:
                        calls_val = c_stat['val']
                    elif c_id == 2:
                        s_calls_val = c_stat['val']
                    elif c_id == 3:
                        f_calls_val = c_stat['val']
                    elif c_id == 4:
                        p_calls_val = c_stat['val']
                    elif c_id == 100: #batch processing time
                        b_processing_time = c_stat['val']
                    elif c_id == 101: #sql execution  time
                        sql_exec_time = c_stat['val']
                    elif c_id == 102: #completion  time
                        completion_exec_time = c_stat['val']
                    elif c_id == 103: #batch count
                        batch_count = c_stat['val']
                    elif c_id == 104: #batch elements
                        batch_elements = c_stat['val']
                    elif c_id == 105: #register count
                        register_counts = c_stat['val']
                key = key // 100
                #print(key)
                #if key == 100000001:
                #    print(unified_key)
                cur_time_val = 0
                cur_calls_val = 0
                cur_s_calls_val = 0
                cur_f_calls_val = 0
                cur_p_calls_val = 0

                cur_b_processing_time = 0
                cur_sql_exec_time = 0
                cur_completion_exec_time = 0
                cur_batch_count = 0
                cur_batch_elements = 0
                cur_register_counts = 0

                if dispatcher in stat['result']['recent'] and core_id in stat['result']['recent'][dispatcher] and op_id in stat['result']['recent'][dispatcher][core_id]:
                    for c_id, c_stat in stat['result']['recent'][dispatcher][core_id][op_id].items():
                        if c_id == 0:
                            cur_time_val = c_stat['val']
                        elif c_id == 1:
                            cur_calls_val = c_stat['val']
                        elif c_id == 2:
                            cur_s_calls_val = c_stat['val']
                        elif c_id == 3:
                            cur_f_calls_val = c_stat['val']
                        elif c_id == 4:
                            cur_p_calls_val = c_stat['val']
                        elif c_id == 100: #batch processing time
                            cur_b_processing_time = c_stat['val']
                        elif c_id == 101: #sql execution  time
                            cur_sql_exec_time = c_stat['val']
                        elif c_id == 102: #completion  time
                            cur_completion_exec_time = c_stat['val']
                        elif c_id == 103: #batch count
                            cur_batch_count = c_stat['val']
                        elif c_id == 104: #batch elements
                            cur_batch_elements = c_stat['val']
                        elif c_id == 105: #register count
                            cur_register_counts = c_stat['val']
                cur_process_rate_val = 0
                cur_rate_val = 0
                if cur_time_val != '-' and cur_calls_val != '-' and cur_p_calls_val != '-' and cur_time_val != 0:
                    cur_process_rate_val = cur_calls_val
                    cur_rate_val = cur_p_calls_val + cur_calls_val
                rate = cur_rate_val - cur_process_rate_val
                rate_sgn = ''
                if rate > 0:
                    rate_sgn = '+'
                if rate < 0:
                    rate_sgn = '-'
                #Log.write('{}:{}:{} = {}'.format(dispatcher, core_id, op_name, val_str))

                table.add_row([key, '{:<2}:{:<2}:{:>10}'.format(dispatcher, core_id, op_name),\
                    '{:10} = {:8} + {:<4} ({:<8})'.format(calls_val, s_calls_val, f_calls_val, time_val), \
                    '{:5} = {:3} + {:<3} ({:<6})'.format(cur_calls_val, cur_s_calls_val, cur_f_calls_val, cur_time_val), \
                    '{:8.2f} / {:<8.2f}'.format(0 if time_val <= 0 else calls_val/time_val, 0 if cur_time_val <= 0 else cur_calls_val/cur_time_val), \
                    '{} ({}{})'.format(p_calls_val, rate_sgn, cur_p_calls_val), \
                    '{:1} + {:4} + {:<2}'.format(cur_b_processing_time, cur_sql_exec_time, cur_completion_exec_time), \
                    '{:4} ({:4}) / {:<2}'.format(cur_batch_count, cur_batch_elements, cur_register_counts) \
                    ])

    table.align['core + op'] = "l"
    table.align['time'] = "l"
    table.align['calls'] = "l"
    table.align['success'] = "l"
    table.align['failed'] = "l"
    table.align['rate'] = "l"
    table.align['pending'] = "l"
    table.padding_width = 1
    Log.write(table.get_string(sortby='id', reversesort=False))
    #print(table)
    return
    #continue
    table = prettytable.PrettyTable([key, "value", "file"])



    last_minute_filter = 100
    total_minute_filter = 1000
    if args.parameter1 is not None:
        last_minute_filter = int(args.parameter1)

    if args.parameter2 is not None:
        total_minute_filter = int(args.parameter2)
    filter = last_minute_filter if key == '1_minute' else total_minute_filter
    for stat_id, state_value in stat_pack.items():
        sorted_stat = reversed(sorted(state_value.items(), key=lambda x:x[1]))
        top_str = ''
        column_name = stat_id
        for i, v in sorted_stat:
            if v > filter:
                table.add_row([column_name, v, desc[i]])
                column_name = ''

    #Log.write(table)


#BEGIN --fix instance status--
def getEpochTime(hours_ago):
    ago = datetime.datetime.now() - datetime.timedelta(hours=hours_ago)
    return int(time.mktime(ago.timetuple()))


def fix_instances(connection):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    Log.write('[FIX_INSTANCES]: {}'.format(creation_time))

    days = 1
    if args.parameter1:
        days = int(args.parameter1)

    hours = 24
    if args.parameter2:
        hours = int(args.parameter2)
    hours_ago = hours * days
    epochTime = getEpochTime(hours_ago)
    activities_pattern = [
        ('^Is', 'string', 'Tol::History::Plain::Activity'),
        ('.State', 'dword', 5),
        ('.Details.CommandID', 'guid', '8F01AC13-F59E-4851-9204-DE1FD77E36B4'),
        ('.Period.FinishTime', 'sqword', epochTime),
        ('.Period.FinishTime^Greater', 'sqword', epochTime)
    ]

    if args.fix_instances != 'all':
        activities_pattern.append(('.Tenant.ID', 'string', args.fix_instances))

    activities_options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Details.CommandID', 'nil', None),
        ('.Mask.Environment.InstanceID', 'nil', None),
        ('.Mask.Environment.ProtectionPlanName', 'nil', None),
        ('.Mask.Tenant', 'nil', None)
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=activities_pattern), options=acrort.plain.Unit(flat=activities_options))
    cache = {}
    table = prettytable.PrettyTable(['id', 'plan', 'host', 'instance', 'tenant'])
    table.padding_width = 1

    def process_activity(connection, activity, cache, act_table):
        source_id = acrobind.get_trait_value('Source', activity)
        id = '{}'.format(activity.ID.ref)
        instance_id = '{}'.format(activity.Environment.InstanceID.ref)
        tenant_id = '{}'.format(activity.Tenant.ID.ref)
        plan_name = ''
        if 'ProtectionPlanName' in activity.Environment:
            plan_name = '{}'.format(activity.Environment.ProtectionPlanName.ref)
        cache[id] = {}
        cache[id]['host_id'] = source_id
        cache[id]['tenant_id'] = tenant_id
        cache[id]['instance_id'] = instance_id

        act_table.add_row([id, plan_name, source_id, instance_id, get_tenant_string(activity)])

    callback = lambda x: process_activity(connection, x, cache, table)
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    if args.extra:
        Log.write(table)
    update_instance_state(connection, cache)


def drop_orphaned_machines(connection):
    locator = '/{}/'.format(args.tenant_id) if args.tenant_id else None
    tenant_locator_to_machines = collections.defaultdict(list)

    def collect_tenant_locators(object):
        Log.write('-', end='')
        tenant_locator_to_machines[object.Tenant.Locator.ref].append(object)

    def tenant_exists(locator):
        Log.write('-', end='')
        spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.Locator', locator)
        return acrobind.count_objects_by_spec(connection, spec) > 0

    spec_m = acrobind.create_viewspec_by_is('MachineManagement::Machine')
    spec_m = acrobind.viewspec_apply_tenant_locator(spec_m, locator)
    mask = acrobind.create_mask2('.Info.Name', '.Tenant')

    Log.write('Collect all MachineManagement::Machine...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_m, mask), collect_tenant_locators, error_log)
    Log.write('\nFound {} tenants'.format(len(tenant_locator_to_machines)))

    Log.write('Search missed Tenants::HierarchyNode...')
    tenant_locator_to_machines = {k: v for k, v in tenant_locator_to_machines.items() if not tenant_exists(k)}

    Log.write('\nFound {} missed Tenants::HierarchyNode.'.format(len(tenant_locator_to_machines)))
    if not tenant_locator_to_machines:
        return

    table = prettytable.PrettyTable(['ID', 'Name', 'Tenant', 'Locator'])
    table.align = 'l'
    table.padding_width = 1
    for key, values in tenant_locator_to_machines.items():
        for v in values:
            tenant = v.get_branch('.Tenant', None)
            table.add_row([str(v.ID.ref), v.Info.Name.ref, tenant.ID.ref if 'ID' in tenant else '-', tenant.Locator.ref])

    Log.write('Orphaned machines:\n{}\nDelete entries?'.format(table.get_string(sortby='Locator')))
    if not ask_user():
        return

    for _, values in tenant_locator_to_machines.items():
        for v in values:
            Log.write('-', end='')
            connection.dml.delete(key=acrort.dml.get_object_key(acrort.plain.Unit(v)))
    Log.write('\nDone.')

    if not args.drop_orphaned_instances:
        Log.write('You deleted orphaned machines. Do you want to delete orphaned instances? (recommended)')
        if ask_user():
            drop_orphaned_instances(connection)

def drop_orphaned_instances(connection):
    host_id_to_keys = collections.defaultdict(list)
    locator = '/{}/'.format(args.tenant_id) if args.tenant_id else None

    def is_aspect(object):
        return acrobind.get_trait_value('Is', object) == 'InstanceManagement::InstanceAspect'

    def collect_host_ids(object):
        Log.write('-', end='')
        host_id = object.get_branch('.Key.HostID' if is_aspect(object) else '.HostID').ref
        host_id_to_keys[str(host_id)].append(object)

    def machine_exists(host_id):
        Log.write('-', end='')
        spec = acrobind.create_viewspec_by_is_and_id('MachineManagement::Machine', host_id)
        return acrobind.count_objects_by_spec(connection, spec) > 0

    spec_i = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    spec_i = acrobind.viewspec_apply_tenant_locator(spec_i, locator)
    spec_ia = acrobind.create_viewspec_by_is('InstanceManagement::InstanceAspect')
    spec_ia = acrobind.viewspec_apply_tenant_locator(spec_ia, locator)
    mask = acrobind.create_mask4('.HostID', '.FullPath', '.Type', '.Tenant')

    Log.write('Collect all InstanceManagement::Instance...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_i, mask), collect_host_ids, error_log)
    Log.write('\nCollect all InstanceManagement::InstanceAspect...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_ia, mask), collect_host_ids, error_log)
    Log.write('\nFound {} hosts'.format(len(host_id_to_keys)))

    Log.write('Search missed MachineManagement::Machine...')
    host_id_to_keys = {k: v for k, v in host_id_to_keys.items() if not machine_exists(k)}

    Log.write('\nFound {} missed MachineManagement::Machine.'.format(len(host_id_to_keys)))
    if not host_id_to_keys:
        return

    table = prettytable.PrettyTable(['HostID', 'Is', 'InstanceID', 'FullPath', 'Type', 'Tenant', 'Locator'])
    table.align = 'l'
    table.padding_width = 1
    for key, values in host_id_to_keys.items():
        for v in values:
            is_trait = acrobind.get_trait_value('Is', v)
            instance_id = str(v.get_branch('.Key.ID' if is_aspect(v) else '.ID').ref)
            tenant = v.get_branch('.Tenant', None)
            table.add_row([key, is_trait, instance_id, v.FullPath.ref, v.Type.ref, tenant.ID.ref if 'ID' in tenant else '-', tenant.Locator.ref])

    Log.write('Orphaned instances:\n{}\nDelete entries?'.format(table.get_string(sort_key=operator.itemgetter(0, 1))))
    if not ask_user():
        return

    for _, values in host_id_to_keys.items():
        for v in values:
            Log.write('-', end='')
            connection.dml.delete(key=acrort.dml.get_object_key(acrort.plain.Unit(v)))
    Log.write('\nDone.')


def update_instance_state(connection, data):
    Log.write('Fixing instance statuses for tenant: \'{}\'.'.format(args.fix_instances))

    mask = acrort.plain.Unit(flat=[
        ('.Mask.FullPath', 'nil', None),
        ('.Mask.Tenant', 'nil', None),
        ('.Mask.BackupState', 'nil', None),
        ('.Mask.BackupStatus', 'nil', None),
        ('.Mask.State', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Availability', 'nil', None),
        ('.Mask.LastBackup', 'nil', None),
        ('.Mask.LastBackupTry', 'nil', None),
        ('.Mask.NextBackupTime', 'nil', None)
    ])

    def set_ams_instance_availability(connection, ams_instance_spec, availability):
        diff = [
            ('', 'dword', availability),
        ]
        diff_unit={'Availability': acrort.plain.Unit(flat=diff)}
        connection.dml.update(pattern=ams_instance_spec.pattern, diff=diff_unit)

    def update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time):
        status = mms_instance['Status']
        if (ams_instance.Status.ref == 1 and mms_instance.Status.ref == 1) or (ams_instance.Status.ref == 0 and mms_instance.Status.ref == 0):
            status = [
            ('', 'dword', 2)
            ]
            status = acrort.plain.Unit(flat=status)
        diff_unit={
        'BackupState': mms_instance['BackupState'],
        'BackupStatus': mms_instance['BackupStatus'],
        'State': mms_instance['State'],
        'Status': status,
        'LastBackup': mms_instance['LastBackup'],
        'LastBackupTry': mms_instance['LastBackupTry'],
        'NextBackupTime': diff_next_start_time,
        }
        Log.write('Applying patch:')
        for name, value in diff_unit.items():
            Log.write('{}: {}'.format(name, str(value)))

        connection.dml.update(pattern=ams_instance_spec.pattern, diff=diff_unit)

    #creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    #fixed_instances_log = 'fixed_instances_log_{}.txt'.format(creation_time)
    #with open(fixed_instances_log, "w") as myfile:
    #    myfile.truncate()

    processed_instances = []
    for activity_id, value in data.items():
        if value['instance_id'] in processed_instances:
            #Log.write('Skipping already processed instance \'{}\' on host \'{}\' (account \'{}\')'.format(value['instance_id'], value['host_id'], value['tenant_id']))
            continue
        else:
            processed_instances.append(value['instance_id'])
            Log.write('Checking instance \'{}\' on host \'{}\' (account \'{}\')'.format(value['instance_id'], value['host_id'], value['tenant_id']))

        ams_instance_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', value['instance_id'])
        ams_instance_spec = acrobind.viewspec_apply_mask(ams_instance_spec, mask)

        ams_instance = acrobind.select_object(connection, ams_instance_spec)
        if ams_instance is None:
            Log.write('AMS doesn\'t have such instance. Skipping.')
            continue

        availability = 13
        if 'Availability' in ams_instance:
            availability = ams_instance.Availability.ref
        mms_instance_spec = acrobind.viewspec_apply_remote_host(ams_instance_spec, value['host_id'])
        mms_instance = None
        try:
            mms_instance = acrobind.select_object(connection, mms_instance_spec)
        except Exception as error:
            #Log.write('Couldn\'t get instance from host {}'.format(value['host_id']))
            if availability != 1:
                Log.write('Reseting Availability property for instance {} to 1.'.format(value['instance_id']))
                if args.fix:
                    set_ams_instance_availability(connection, ams_instance_spec, 1)
            continue

        #print(ams_instance)
        if mms_instance is None:
            Log.write('Instance {} is missing on host {}'.format(value['instance_id'], value['host_id']))
            continue

        if availability != 0:
            Log.write('Reseting Availability property for instance {} to 0.'.format(value['instance_id']))
            if args.fix:
                set_ams_instance_availability(connection, ams_instance_spec, 0)

        table = prettytable.PrettyTable(['Property', 'ams', 'mms', 'equal'])
        table.padding_width = 1
        def process_property(prop_name, ams_object, mms_object, table):
            ams_prop_state = None
            if prop_name in ams_object:
                ams_prop_state = ams_object[prop_name]
            mms_prop_state = None
            if prop_name in mms_object:
                mms_prop_state = mms_object[prop_name]

            equal = (ams_prop_state == mms_prop_state)
            ams_prop_state_str = '-'
            if ams_prop_state is not None and ams_prop_state.is_composite():
                ams_prop_state_str = '{}'.format(ams_prop_state)
            else:
                ams_prop_state_str = '{}'.format(ams_prop_state.ref)
            mms_prop_state_str = '-'
            if mms_prop_state is not None and mms_prop_state.is_composite():
                mms_prop_state_str = '{}'.format(mms_prop_state)
            else:
                mms_prop_state_str = '{}'.format(mms_prop_state.ref)
            table.add_row([prop_name, ams_prop_state_str, mms_prop_state_str, '+' if equal else '-'])
            return equal

        instance_name = ''
        tenant_str = get_tenant_string(ams_instance)
        if 'FullPath' in ams_instance:
            instance_name = ams_instance.FullPath.ref

        if 'FullPath' not in mms_instance or \
            'BackupState' not in mms_instance or \
            'BackupStatus' not in mms_instance or \
            'State' not in mms_instance or \
            'Status' not in mms_instance or \
            'LastBackup' not in mms_instance or \
            'LastBackupTry' not in mms_instance or \
            'NextBackupTime' not in mms_instance:
            Log.write('CORRUPTED MMS INSTANCE PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
            #print(mms_instance)
            continue

        instance_ok = True
        instance_ok = instance_ok and process_property('FullPath', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('BackupState', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('BackupStatus', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('State', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('Status', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('LastBackup', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('LastBackupTry', ams_instance, mms_instance, table)

        diff_next_start_time_unit = None
        if 'NextBackupTime' in mms_instance and 'Time' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Time.ref is not None:
            mms_next_start_time = mms_instance.NextBackupTime.Time.ref
            ams_next_start_time = 0
            if 'NextBackupTime' in ams_instance and 'Time' in ams_instance.NextBackupTime and ams_instance.NextBackupTime.Time.ref is not None:
                ams_next_start_time = ams_instance.NextBackupTime.Time.ref
                time_equal = (mms_next_start_time == ams_next_start_time)
                instance_ok = instance_ok and time_equal
                #print('{} {}'.format(mms_next_start_time, ams_next_start_time))
            else:
                #print('FALSE')
                time_equal = False
                instance_ok = False

            table.add_row(['NextStartTime', '{}'.format(ams_next_start_time), '{}'.format(mms_next_start_time), '+' if time_equal else '-'])
            diff_next_start_time = [
                ('.Time', 'sqword', mms_next_start_time),
                ('^Is', 'string', 'Gtob::Dto::NextExecutionTime')
            ]
            if 'Trigger' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Trigger.ref is not None:
                diff_next_start_time.append(('.Trigger', 'dword', mms_instance.NextBackupTime.Trigger.ref))
            diff_next_start_time_unit = acrort.plain.Unit(flat=diff_next_start_time)
        else:
            diff_next_start_time_unit = ams_instance.NextBackupTime

        if not instance_ok:
            Log.write('SYNC PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
            Log.write(table)
            if args.fix:
                update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time_unit)
            #with open(fixed_instances_log, "a") as myfile:
            #    myfile.write('{}\n'.format(value['instance_id']))

        if instance_ok:
            if (ams_instance.Status.ref == 1 and mms_instance.Status.ref == 1) or (ams_instance.Status.ref == 0 and mms_instance.Status.ref == 0):
                Log.write('STATUS PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
                Log.write(table)
                if args.fix:
                    update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time_unit)

        if 'LastBackup' in mms_instance and 'NextBackupTime' in mms_instance and 'Time' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Time.ref is not None:
            try:
                if mms_instance.LastBackup.ref > mms_instance.NextBackupTime.Time.ref:
                    Log.write('SCHEDULE PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
                    Log.write(table)
            except Exception as error:
                print(mms_instance.NextBackupTime)
                print(error)
#END --fix instance status--


def check_sync(connection):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    Log.write('[FIX_INSTANCES]: {}'.format(creation_time))

    agents_pattern = [
        ('^Is', 'string', 'MachineManagement::Machine'),
        ('.Info.Role', 'dword', 0),
        ('.Status', 'dword', 0)
    ]

    if args.check_sync != 'all':
        agents_pattern.append(('.Tenant.ID', 'string', args.check_sync))

    options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Info.Name', 'nil', None),
        ('.Mask.Tenant', 'nil', None)
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=agents_pattern), options=acrort.plain.Unit(flat=options))

    processed_agents = []
    flag = False
    def process_agent(connection, cache, wait_flag, agent):
        #print(agent)
        host_id = agent.ID.ref
        if host_id in cache:
            Log.write('.', end='')
            wait_flag = True
            return
        else:
            if wait_flag:
                Log.write('.')
                wait_flag = False
            cache.append(host_id)

        name_str = '-'
        try:
            name_str = agent.Info.Name.ref
        except:
            pass

        if 'Tenant' not in agent:
            Log.write('MISSING tenant in {}, {}, {}'.format(name_str, host_id, get_tenant_string(agent)))
            return

        tenant_id = '{}'.format(agent.Tenant.ID.ref)
        #print(host_id)

        ams_activities_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.State', 'dword', 5),
            #('^Source', 'string', '{}'.format(host_id)),
            ('.Details.MachineName', 'string', name_str),
            ('.Tenant.ID', 'string', tenant_id),
            ('.Details.Specific', 'string', 'Business'),
        ]

        agent_activities_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.__source_machine', 'guid', agent.ID.ref),
            ('.State', 'dword', 5),
            ('.Details.Specific', 'string', 'Business'),
        ]

        options = [
            ('.SortingOptions.Period.FinishTime', 'sqword', 0),
            ('.SortingOptions.Period.FinishTime^Descending', 'nil', None),
            ('.LimitOptions', 'dword', 1),
            ('.Mask.ID', 'nil', None),
            ('.Mask.Tenant', 'nil', None),
            ('.Mask.Details.MachineName', 'nil', None),
            ('.Mask.Period.FinishTime', 'nil', None)
            ]

        ams_act_spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=ams_activities_pattern), options=acrort.plain.Unit(flat=options))
        mms_act_spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=agent_activities_pattern), options=acrort.plain.Unit(flat=options))

        ams_act = acrobind.select_object(connection, ams_act_spec)
        #print(ams_act)
        ams_act_time = 0
        if ams_act is not None and 'Period' in ams_act and 'FinishTime' in ams_act.Period:
            ams_act_time = ams_act.Period.FinishTime.ref

        mms_act = None
        try:
            mms_act = acrobind.select_object(connection, mms_act_spec)
        except Exception as error:
            #Log.write('Couldn\'t get instance from host {}'.format(value['host_id']))
            pass

        mms_act_time = 0
        if mms_act is not None and 'Period' in mms_act and 'FinishTime' in mms_act.Period:
            mms_act_time = mms_act.Period.FinishTime.ref

        if ams_act_time < mms_act_time:
            Log.write('NEED RESYNC: {}, {}, {}, {}, {}'.format(ams_act_time, mms_act_time, name_str, host_id, get_tenant_string(agent)))
            if args.fix:
                if args.parameter1:
                    psql_command = []
                    psql_command.append('psql')
                    psql_command.append('acronis_cms')
                    psql_command.append('-c')
                    psql_command.append('update attachedmachines set lastoperationid=1 where machineid=\'{}\''.format(host_id))
                    ret = subprocess.call(psql_command)
                    Log.write('Update last operation id: {}'.format(ret))

                drop_agent_connection(connection, host_id)
        else:
            Log.write('OK: {}, {}, {}, {}, {}'.format(ams_act_time, mms_act_time, name_str, host_id, get_tenant_string(agent)))

    callback = lambda x: process_agent(connection, processed_agents, flag, x)
    acrobind.enumerate_objects(connection, spec, callback, error_log)


def sync_monitor(connection):

    sync_monitor_for = args.sync_monitor
    names = {
      '7': 'Instance',
      '77': 'Aspect',
      '9': 'Machine',
      '15': 'Plan',
      '17': 'CentralizedItemProtection',
      '18': 'LocalItemProtection',
      '29': 'Activity',
      '101': 'Configuration',
      '102': 'Applications',
      '103': 'Cluster',
      '104': 'VmRessurection',
      '105': 'Archive',
      '106': 'Slice',
      '107': 'Vault',
      '108': 'Location',
      '109': 'Alert',
      '110': 'Notification',
      '111': 'Autoupdate',
      '112': 'Counter',
      '113': 'ProcessInfo',
      '114': 'UpgradeEvent11',
      '115': 'LocalPlan',
      '116': 'LocalProtectionObject',
      '117': 'Migration'
    }

    def format_epoch(x):
        if x is None:
            return 'Never'
        if x == 0:
            return 'Never'
        t = time.localtime(x)
        return time.strftime('%Y-%m-%d %H:%M:%S', t)

    def print_process_report(data):
        result = prettytable.PrettyTable(["ID", "Commits", "Reads", "RR", "Changed", "News", "Completions", "Objects", "Expired", "Fails"])
        result.align = "r"
        result.align["ID"] = "l"

        for x in data:
            is_summary = [unit.ref for name, unit in x.traits if unit.ref == "Sync::Replication::Monitoring::SummaryCounter"]
            if not is_summary and 'Processes' in x:
                for name, p in x.Processes:
                    if name in names:
                        session_id = '-'
                        if 'SessionID' in x:
                            session_id = str(x.SessionID.ref)
                        result.add_row([
                            "/{}/{}/{}".format(str(x.MachineID.ref), session_id, names[name]),
                            p.CommitCount.ref,
                            p.ReadCount.ref,
                            p.RemoteReadCount.ref,
                            format_epoch(p.SourceChanged.ref),
                            get_optional(p, 'ReadNewsCount'),
                            get_optional(p, 'CompletionCount'),
                            p.ObjectCount.ref,
                            get_optional(p, 'ExpiredObjectsCount'),
                            p.FailCount.ref
                        ])
        Log.write(result.get_string(sortby="ID", reversesort=False))

    def print_session_report(data):
        result = prettytable.PrettyTable(["ID", "StartTime", "InPackets", "InSize/K", "OutPackets", "OutSize/K",
                                          "Commits", "CommitTime", "Reads", "ReadTime", "RR", "RRTime",
                                          "Changed", "Recon", "Fails"])
        result.float_format["InSize/K"] = ".2"
        result.float_format["OutSize/K"] = ".2"
        result.align = "r"
        result.align["ID"] = "l"

        for x in data:
            is_summary = [unit.ref for name, unit in x.traits if unit.ref == "Sync::Replication::Monitoring::SummaryCounter"]
            if is_summary:
                result.add_row([
                    'Summary',
                    '-',
                    x.InCount.ref,
                    x.InSize.ref / 1024,
                    x.OutCount.ref,
                    x.OutSize.ref / 1024,
                    x.CommitCount.ref,
                    x.CommitTime.ref / 1000,
                    x.ReadCount.ref,
                    x.ReadTime.ref / 1000,
                    '-',
                    '-',
                    '-',
                    '-',
                    x.FailCount.ref
                ])
            else:
                result.add_row([
                    "/{}/{}".format(str(x.MachineID.ref), str(get_optional(x, 'SessionID'))),
                    format_epoch(get_optional(x, 'StartTime')),
                    x.InCount.ref,
                    x.InSize.ref / 1024,
                    x.OutCount.ref,
                    x.OutSize.ref / 1024,
                    x.CommitCount.ref,
                    x.CommitTime.ref / 1000,
                    x.ReadCount.ref,
                    x.ReadTime.ref / 1000,
                    get_optional(x, 'RemoteReadCount'),
                    '-' if get_optional(x, 'RemoteReadTime') is None else str(get_optional(x, 'RemoteReadTime') / 1000),
                    format_epoch(get_optional(x, 'Changed')),
                    get_optional(x, 'ReconcileTime'),
                    x.FailCount.ref
                ])

        Log.write(result.get_string(sortby="ID", reversesort=False))


    def apply_limits(spec):
        if args.parameter1 is not None and args.parameter2 is not None:
            limit_pattern = [
                ('.{0}'.format(args.parameter1), 'dword', int(args.parameter2))
            ]
            return acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return spec


    if sync_monitor_for == 'summary':
        pattern = [
            ('^Is', 'string', 'Sync::Replication::Monitoring::Counter'),
            ('^Is', 'string', 'Sync::Replication::Monitoring::SummaryCounter')
        ]
        data = acrobind.select_objects(connection, apply_limits(acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))))
        print_session_report(data)

    elif sync_monitor_for == 'all':
        spec = apply_limits(acrobind.create_viewspec_by_is('Sync::Replication::Monitoring::Counter'))
        print(spec.pattern)
        data = acrobind.select_objects(connection, spec)
        print_session_report(data)
        print_process_report(data)
    else:
        data = acrobind.select_objects(connection, apply_limits(acrobind.create_viewspec_by_is_and_guid_property('Sync::Replication::Monitoring::Counter', '.MachineID', sync_monitor_for)))
        print_session_report(data)
        print_process_report(data)


def is_deprecated_vm_instance(id):
    deprecated_ids = [
        '98259016-909E-48dd-A240-EE97209F545C',
        'E7F120F4-5479-4C91-AEA0-ACE049E8F4CC',
        '1052D468-8EA9-6C59-A0DB-9E56FC6A23C6',
        'ADFC498F-C6A4-AF0B-0476-277362346360',
        'B7A68552-D940-4781-B4CD-95F178DA7B2C'
    ]
    return id in deprecated_ids;


def check_deprecated_vms(connection):
    deprecated_ids = [
        '98259016-909E-48dd-A240-EE97209F545C',
        'E7F120F4-5479-4C91-AEA0-ACE049E8F4CC',
        '1052D468-8EA9-6C59-A0DB-9E56FC6A23C6',
        'ADFC498F-C6A4-AF0B-0476-277362346360',
        'B7A68552-D940-4781-B4CD-95F178DA7B2C'
    ]

    instances_spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    instances_spec = acrobind.viewspec_apply_ids(instances_spec, deprecated_ids)

    instance_aspects_spec = acrobind.create_viewspec_by_is('InstanceManagement::InstanceAspect')

    ids_pattern = []
    for id in deprecated_ids:
        ids_pattern.append([('', 'guid', id)])

    pattern = [
        ('.Key.ID', 'guid', '00000000-0000-0000-0000-000000000000'),
        ('.Key.ID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
    ]

    instance_aspects_spec = acrort.dml.ViewSpec(instance_aspects_spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), instance_aspects_spec.options)

    objects = acrobind.select_objects(connection, instances_spec)
    #dump instances
    for i in objects:
        Log.write(i)

    aspects = acrobind.select_objects(connection, instance_aspects_spec)
    for i in aspects:
        Log.write(i)

    if args.fix:
        Log.write('Removing deprecated aspects and instances...')
        start = time.time()
        connection.dml.delete(pattern=instance_aspects_spec.pattern)
        connection.dml.delete(pattern=instances_spec.pattern)
        Log.write('Elapsed: {0:.2f} s'.format(time.time() - start))


def check_status(connection_args):
    Log.write('')
    start = time.time()
    connection = acrort.connectivity.Connection(*connection_args)
    Log.write('Connection time: {0:.2f} s'.format(time.time() - start))

    table = prettytable.PrettyTable(["DB", "Connection Time (s)"])
    table.align["DB"] = "l"
    table.align["Connection Time (s)"] = "l"
    table.padding_width = 1

    def check_db_connection(connection, object, table):
        options = [('.LimitOptions', 'dword', 1)]
        start = time.time()
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=[('^Is', 'string', object)]), options=acrort.plain.Unit(flat=options))
        objects = acrobind.select_objects(connection, spec)
        table.add_row([object, '{0:.2f}'.format(time.time() - start)])

    check_db_connection(connection, 'Tenants::HierarchyNode', table)
    check_db_connection(connection, 'Gtob::Dto::ItemProtection', table)
    check_db_connection(connection, 'Tol::History::Plain::Activity', table)
    check_db_connection(connection, 'MachineManagement::Machine', table)
    check_db_connection(connection, 'InstanceManagement::Instance', table)
    check_db_connection(connection, 'Agent::Configuration', table)
    check_db_connection(connection, 'Tenant::Configuration', table)
    check_db_connection(connection, 'GroupManagement::Group', table)
    check_db_connection(connection, 'ArchiveManagement::Archive', table)
    check_db_connection(connection, 'Msp::AMS::Dto::Machine', table)

    Log.write(table.get_string(sortby="Connection Time (s)", reversesort=True))
    Log.write('')

    if args.extra:
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=[('^Is', 'string', 'Msp::Agent::Dto::Configuration')]))
        object = acrobind.select_object(connection, spec)
        table = prettytable.PrettyTable(["Name", "Value"])
        table.align["Name"] = "l"
        table.padding_width = 1
        table.add_row(["AgentID", str(get_optional(object, 'AgentID'))])
        table.add_row(["ZmqPublicKey", str(get_optional(object.Zmq, 'ZmqPublicKey'))])
        table.add_row(["Uplink", '{0}:{1}'.format(str(get_optional(object.Uplink.Address, 'Address')), str(get_optional(object.Uplink.Address, 'Port')))])
        Log.write(table.get_string(sortby="Name"))
        Log.write('')


def check_notifications():
    '''
    Print statistics about backup notifications
    '''
    def group(name, pattern):
        return '(?P<{}>{})'.format(name, pattern)


    def convert_date(date):
        return datetime.datetime.strptime(date, '%Y-%m-%d %H:%M:%S:%f')


    def calculate_average(avg, current, count):
        if count == 1:
            return avg + current
        else:
            return avg * (count - 1) / count + current / count


    date_pattern = r'\d[\d-]+ \d[\d:]+'
    thread_id_pattern = r'\d+'
    level_pattern = r'[\w]\d+'

    log_header_pattern = '^{} {} {}:'.format(group('date', date_pattern), thread_id_pattern, level_pattern)

    guid_pattern = '[0-9A-F-]+'
    activity_header_pattern = r'\[Activity\] ID: {}'.format(group('activity_id', guid_pattern))

    start_string = 'Backup reports: processing completed activity:'
    end_string = 'Backup reports: MarkProcessedAndFlushActivities'

    activity_start_line = '{} {} {}'.format(log_header_pattern, start_string, activity_header_pattern)
    activity_end_line = '{} {} {}'.format(log_header_pattern, end_string, activity_header_pattern)

    log_file = os.path.join(acrort.fs.APPDATA_COMMON, acrort.common.BRAND_NAME, 'AMS', 'backup_notifications.0.log')

    with open(log_file, encoding='latin-1') as log:
        activities = {}
        durations = []

        avg_time = datetime.timedelta()
        completed_count = 0

        line_number = 0
        for line in log:
            line_number += 1
            r = re.search(activity_start_line, line)
            if r:
                activity_id = r.group('activity_id')
                activities[activity_id] = {'id': activity_id, 'start': convert_date(r.group('date'))}
            r = re.search(activity_end_line, line)
            if r:
                activity_id = r.group('activity_id')
                data = activities.get(activity_id)
                if data:
                    completed_count += 1
                    data['end'] = convert_date(r.group('date'))
                    data['duration'] = data['end'] - data['start']

                    avg_time = calculate_average(avg_time, data['duration'], completed_count)

                    durations.append(data)

        s = sorted(durations, key=lambda x: x['duration'])

        Log.write('Slowest processed activities:')
        for idx, record in enumerate(reversed(s[-5:])):
            Log.write(idx + 1, 'time:', str(record['duration']), ' activity:', record['id'])
        Log.write()

        Log.write('Fastest processed activities:')
        for idx, record in enumerate(s[:5]):
            Log.write(idx + 1, 'time:', str(record['duration']), ' activity:', record['id'])
        Log.write()

        Log.write('Average activity processing time:', avg_time)
        Log.write('Count of analysed activities: ', completed_count)


def delete_object(connection, args):
    spec = acrort.plain.Unit(flat=[('.ID', 'guid', args.delete_object)])
    connection.dml.delete(pattern=spec)


def fix_all_centralized_protections(connection):
    Log.write('Do you want to fix Gtob::Dto::CentralizedProtection objects for all tenants?(y/n)')
    if not ask_user():
        return

    plans_pattern = [
        ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
        ('.Origin', 'dword', 2), # Centralized plans only
    ]

    options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Origin', 'nil', None),
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=plans_pattern), options=acrort.plain.Unit(flat=options))

    plans = []
    acrobind.enumerate_objects(connection, spec, lambda x: plans.append(x), error_log)
    Log.write('Going to check {} plans'.format(len(plans)))

    fix_centralized_protections(connection, plans)


def report_all_instances(connection):
    Log.write(';'.join(['ID', 'BackupState', 'BackupStatus', 'FullPath', 'HostID', 'Mobile', 'AD', 'ATIH', 'BackupAgent', 'SQL', 'Exchange', 'ESX', 'Hyper-V', 'LastBackup', 'NextBackup', 'State', 'Status', 'Type']))

    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'InstanceManagement::Instance'),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.BackupState', 'nil', None),
        ('.Mask.BackupStatus', 'nil', None),
        ('.Mask.FullPath', 'nil', None),
        ('.Mask.HostID', 'nil', None),
        ('.Mask.LastBackup', 'nil', None),
        ('.Mask.NextBackupTime.Time', 'nil', None),
        ('.Mask.State', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Type', 'nil', None),
        ('.Mask.HostInfo.Agents', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(pattern, options)

    def print_instance(instance):
        is_esx = False
        is_hyperv = False
        is_mobile = False
        is_ad = False
        is_ati = False
        is_win = False
        is_sql = False
        is_exchange = False
        is_hyperv = False
        if 'HostInfo' in instance and 'Agents' in instance.HostInfo:
            is_mobile = is_mobile_agent(instance.HostInfo.Agents)
            is_ad = is_ad_agent(instance.HostInfo.Agents)
            is_ati = is_ati_agent(instance.HostInfo.Agents)
            is_win = is_win_agent(instance.HostInfo.Agents)
            is_sql = is_sql_agent(instance.HostInfo.Agents)
            is_exchange = is_exchange_agent(instance.HostInfo.Agents)
            is_esx = is_esx_agent(instance.HostInfo.Agents)
            is_hyperv = is_hyperv_agent(instance.HostInfo.Agents)

        last_backup_stamp = instance.LastBackup.ref if instance.LastBackup.ref else 0
        next_backup_stamp = instance.NextBackupTime.Time.ref if instance.NextBackupTime.Time.ref else 0

        record = [
            instance.ID.ref,
            BACKUP_STATES.get(instance.BackupState.ref),
            BACKUP_STATUSES.get(instance.BackupStatus.ref),
            instance.FullPath.ref,
            instance.HostID.ref,
            '+' if is_mobile else '-',
            '+' if is_ad else '-',
            '+' if is_ati else '-',
            '+' if is_win else '-',
            '+' if is_sql else '-',
            '+' if is_exchange else '-',
            '+' if is_esx else '-',
            '+' if is_hyperv else '-',
            datetime.datetime.utcfromtimestamp(last_backup_stamp),
            datetime.datetime.utcfromtimestamp(next_backup_stamp),
            INSTANCE_STATES.get(instance.State.ref) if 'State' in instance else None,
            INSTANCE_STATUSES.get(instance.Status.ref),
            INSTANCE_TYPES.get(instance.Type.ref),
        ]

        print(';'.join([str(field) for field in record]).encode('utf-8').strip())

    acrobind.enumerate_objects(connection, spec, print_instance, error_log)


def format_epoch(x):
    t = time.localtime(x)
    return time.strftime('%Y-%m-%d %H:%M:%S', t)


def collect_protections(connection, tenants):
    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'Gtob::Dto::Centralized::ItemProtection'),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Centralized.PlanID', 'nil', None),
        ('.Mask.HostID', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(pattern, options)
    spec = acrobind.viewspec_apply_tenants(spec, tenants)
    stat = {}
    protections = [x for x in acrobind.dml_utils.enum_objects(connection, spec)]

    for x in protections:
        agent = ''

        if 'HostID' in x:
            agent = str(x.HostID.ref)
        else:
            for name, trait_value in x.traits:
                if 'name' == 'Source':
                    agent = str(trait_value.ref)

        if agent in stat:
            stat[agent] += 1
        else:
            stat[agent] = 1
    return stat


def collect_tenants(connection, tenants):
    pattern = [
        ('^Is', 'string', 'Tenants::HierarchyNode'),
    ]

    if tenants:
        ids_pattern = []
        for tenant_id in tenants:
            ids_pattern.append([('', 'string', tenant_id)])

        pattern.append(('.Tenant.ID', 'string', ''))
        pattern.append(('.Tenant.ID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]))

    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Name', 'nil', None),
        ('.Mask.Locator', 'nil', None),
        ('.Mask.Parent', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern), options)
    tenants = { str(x.ID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec) }
    return { id: tenant.Name.ref for id, tenant in tenants.items() }


def collect_public_keys(connection, tenants):
    pattern = [
        ('^Is', 'string', 'Msp::AMS::Dto::Machine'),
    ]

    if tenants:
        ids_pattern = []
        for tenant_id in tenants:
            ids_pattern.append([('', 'string', tenant_id)])

        pattern.append(('.OwnerID', 'string', ''))
        pattern.append(('.OwnerID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]))

    options = acrort.plain.Unit(flat=[
        ('.Mask.AgentID', 'nil', None),
        ('.Mask.IsEnabled', 'nil', None),
        ('.Mask.OwnerID', 'nil', None),
        ('.Mask.PublicKey', 'nil', None),
        ('.Mask.LastSeenTime', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern), options)
    return { str(x.AgentID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec)  }


def collect_offline_machines(connection, tenants):
    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'MachineManagement::Machine'),
        ('.Info.Role', 'dword', 0),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Info.Agents', 'nil', None),
        ('.Mask.Info.Hardware.MemorySize', 'nil', None),
        ('.Mask.Info.Hardware.ProcessorName', 'nil', None),
        ('.Mask.Info.Hardware.ProcessorFrequency', 'nil', None),
        ('.Mask.Info.Name', 'nil', None),
        ('.Mask.Info.OS.ArchitectureEdition', 'nil', None),
        ('.Mask.Info.OS.Name', 'nil', None),
        ('.Mask.Info.OS.OSCaps', 'nil', None),
        ('.Mask.Info.OS.OSType', 'nil', None),
        ('.Mask.Info.OS.ProductType', 'nil', None),
        ('.Mask.Info.ResidentialAddresses', 'nil', None),
        ('.Mask.LastConnectionTime', 'nil', None),
        ('.Mask.MachineAddress', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Tenant', 'nil', None),
        ('.Mask.UpdateState.UpdateIsAvailable', 'nil', None)
    ])
    spec = acrort.dml.ViewSpec(pattern, options)
    spec = acrobind.viewspec_apply_tenants(spec, tenants)
    return { str(x.ID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec) }


def compare_numbers(lhs, rhs):
    if lhs > rhs:
        return 1
    elif lhs == rhs:
        return 0
    else:
        return -1


def compare_versions(lhs, rhs):
    major, minor, build = lhs.split('.')
    major2, minor2, build2 = rhs.split('.')
    if major == major2:
        if minor == minor2:
            return compare_numbers(build, build2)
        else:
            return compare_numbers(minor, minor2)
    return compare_numbers(major, major2)


def get_product_type(build_number):
    if len(build_number) <= 2:
        return "mobile"
    versions = build_number.split('.')
    if int(versions[0]) < 11:
        return "mobile"
    elif int(versions[0]) == 1:
        return "msp"
    elif int(versions[2]) < 100:
        return "home"
    else:
        return "msp"


def get_agent_version(machine):
    version = machine.Info.Agents[0].Version.ref
    for agent in machine.Info.Agents:
        agent = agent[1]
        agent_version = agent.Version.ref
        if agent_version.count('.') != 2:
            continue
        if compare_versions(agent_version, version) > 0:
            version = agent_version
    return version


def get_os_caps(value):
    if value & 4:
        return 'APPLIANCE'
    if value & 1:
        return 'BOOTMEDIA'
    if value & 8:
        return 'LINUX_RAMDISK'
    if value & 16:
        return 'HYPERV'
    if value & 32:
        return 'DR_APPLIANCE'
    if value & 2:
        return 'SERVER'
    return ''


def get_additional_info(value):
    if value & 2:
        return 'DOMAIN CONTROLLER'
    if value & 3:
        return 'SERVER'
    if value & 1:
        return 'WORKSTATION'
    return ""


def report_all_machines(connection):
    tenants = []
    if args.tenant_id:
        tenants.append(args.tenant_id)
        Log.write('Checking subtenants of {0}'.format(args.tenant_id))
        tenant_spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.tenant_id)
        t = acrobind.select_object(connection, tenant_spec)
        if t:
            pattern = [
                ('^Is', 'string', 'Tenants::HierarchyNode'),
                ('.Locator', 'string', ''),
                ('.Locator^DirectRelative', 'string', t.Locator.ref)
            ]
            tenants_objects = acrobind.select_objects(connection, acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern)))
            for ten in tenants_objects:
              tenants.append(ten.ID.ref)

    protections = collect_protections(connection, tenants)
    tenant_names = collect_tenants(connection, tenants)
    public_keys = collect_public_keys(connection, tenants)
    offline_machines = collect_offline_machines(connection, tenants)

    print(';'.join(["Account", "EUC", "AccountName", "AgentID", "MachineName", "IsEnabled", "PublicKey", "DmlTimeStamp", "OSName", "OS", "Architecture", "OSCaps", "OSInfo", "Memory",
      "ProcessorName", "ProcessorFrequency", "IP", "Build", "Product", "IsUpdateAvailable", "PlanCount", "LastConnectionTimeOld", "DaysOfflineOld", "LastConnectionTime", "DaysOffline", "Status"]))
    report = []
    for agent_id, machine in offline_machines.items():
        agent_version = get_agent_version(machine)
        product = get_product_type(agent_version)

        machine_name = machine.Info.Name.ref
        last_connection_time_old = 0
        sec_offline_old = 0
        if 'LastConnectionTime' in machine:
            last_connection_time_old = format_epoch(machine.LastConnectionTime.ref)
            sec_offline_old = int(time.time()) - machine.LastConnectionTime.ref

        if agent_id not in public_keys:
            continue
        key = public_keys.get(agent_id, None)
        if not key:
            print('Cannot find key for {}({})'.format(machine_name, agent_id))
            continue
        if 'OwnerID' in key:
            owner_id = str(key.OwnerID.ref)
        if 'Tenant' not in machine:
            print('Cannot find account for {}({}), owner={}, name={}'.format(machine_name, agent_id, owner_id, tenant_names.get(owner_id, 'Not found')))
            continue

        last_connection_time = 0
        sec_offline = 0
        if 'LastSeenTime' in key:
            last_connection_time = format_epoch(key.LastSeenTime.ref)
            sec_offline = int(time.time()) - key.LastSeenTime.ref

        stamp = key.DmlTimeStamp.ref
        if 'Status' in machine:
            is_offline = machine.Status.ref == 1
        else:
            is_offline = True
        is_enabled = key.IsEnabled.ref
        os = {1: "Unknown", 2: "Windows", 3: "Windows", 4: "Linux", 5: "MacOS"}[machine.Info.OS.OSType.ref]
        os_name = machine.Info.OS.Name.ref
        architecture = {0: "Unknown", 1: "x86", 2: "x64"}[machine.Info.OS.ArchitectureEdition.ref]
        os_caps = ""
        if 'OSCaps' in machine.Info.OS:
            os_caps = get_os_caps(machine.Info.OS.OSCaps.ref)
        os_info = ""
        if 'ProductType' in machine.Info.OS:
            os_info = get_additional_info(machine.Info.OS.ProductType.ref)
        memory = 0
        processor = ""
        if 'Hardware' in machine.Info:
            memory = round(machine.Info.Hardware.MemorySize.ref / 1024 / 1024)
            processor = machine.Info.Hardware.ProcessorName.ref
            frequency = machine.Info.Hardware.ProcessorFrequency.ref
        update_available = False
        if 'UpdateState' in machine:
            update_available = machine.UpdateState.UpdateIsAvailable.ref

        address = ','.join([y.ref for i, y in machine.Info.ResidentialAddresses])
        public_key = ''
        plan_count = protections.get(agent_id, 0)
        if 'PublicKey' in key:
            public_key = str(key.PublicKey.ref)

        account = machine.Tenant.Locator.ref
        account_name = tenant_names.get(machine.Tenant.ID.ref, 'Not found')
        euc_name = tenant_names.get(machine.Tenant.ParentID.ref, 'Not found')
        report.append([account, euc_name, account_name, agent_id, machine_name, is_enabled, public_key, stamp, os_name, os, architecture, os_caps, os_info, memory, processor,
            frequency, address, agent_version, product, update_available, plan_count, last_connection_time_old, round(sec_offline_old / 86400), last_connection_time, round(sec_offline / 86400), is_offline])
    for r in report:
        print(';'.join([str(c) for c in r]))


def main():
    parser = acrobind.CommandLineParser()
    parser.append_processor(acrobind.OutputArgumentsProcessor())
    parser.add_argument('--connection', nargs=3, metavar=('HOST', 'USERNAME', 'PASSWORD'))

    #resource specific options
    parser.add_argument('-mi', '--check-machine-id', dest='machine_id', help='Show info about machine by its ID and related instances.\
    Use \'-f\' option to enable fix mode. Use \'-ru\' for resetting \'MachineIsProcessed\' property. \
    Use \'-ra\' to list running activities. Use \'-br\' for dropping connection. \
    Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE\'.', required=False)
    parser.add_argument('-mn', '--check-machine-name', dest='machine_name', help='List all machines that match provided name.\
    Example: \'acropsh -m amsctl -mn MyMachine\'.', required=False)
    parser.add_argument('-ru', '--reset-update', dest='reset_update', action='store_true', help='Reset \'MachineIsProcessed\' property for MachineManagement::Machine.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -ru\'.', required=False)
    parser.add_argument('-ra', '--running-activities', dest='running_activities', action='store_true', help='List all running activities for tenant or machine if it is ONLINE.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -ra\'.', required=False)
    parser.add_argument('-br', '--break-connection', dest='break_connection', action='store_true', help='Drop connection with agent if it is connected.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -br\'.', required=False)
    parser.add_argument('-in', '--check-instance-name', dest='instance_name', help='List all instances that match provided name.', required=False)
    parser.add_argument('-ii', '--check-instance-id', dest='instance_id', help='Show info about instance by its ID ans applied protections.', required=False)
    parser.add_argument('-dom', '--drop-orphaned-machines', dest='drop_orphaned_machines', help='Remove machines without tenant.\
    Can be used with \'-ti\' option.', action='store_true', required=False)
    parser.add_argument('-doi', '--drop-orphaned-instances', dest='drop_orphaned_instances', help='Remove instances without host.\
    Can be used with \'-ti\' option.', action='store_true', required=False)

    #tenant specific options
    parser.add_argument('-ti', '--check-tenant-id', dest='tenant_id', help='Show info about specified tenant and its related objects.\
    Use \'-qr\' for recalculating quotas usage. Use \'-ms\' for showing statistics object for this tenant. Use \'-ra\' for running activities. Example: \'acropsh -m amsctl -ti 13456\'.', required=False)
    parser.add_argument('-tn', '--check-tenant-name', dest='tenant_name', help='List tenants that match specified name.', required=False)
    parser.add_argument('-qr', '--quotas-reconcile', dest='quotas_reconcile', action='store_true', help='Recalculate quotas for account.\
    Can be used with \'-ti\' only. Use \'all\' for reconcile quotas on all accounts. Example: \'acropsh -m amsctl -ti 13456 -qr\'.', required=False)
    parser.add_argument('-ms', '--machine-statistics', dest='machine_statistics', action='store_true', help='Show MachineManagement::Statistics for account.\
    Can be used with \'-ti\' only. Example: \'acropsh -m amsctl -ti 13456 -ms\'.', required=False)
    parser.add_argument('--check-multitenancy', dest='check_multitenancy', action='store_true', help='Check if tenant has virtual instances that belong to hosts of different tenant')
    parser.add_argument('-u', '--update', dest='update', action='store_true', help='Update section \'.Tenant\' in all dml objects by specified tenant')
    #plans specific options
    parser.add_argument('-pn', '--plan-name', dest='plan_name', help='List all protection plans that match specified name.', required=False)
    parser.add_argument('-pi', '--plan-id', dest='plan_id', help='Show info about protection plan and related item protections.\
    Use \'-r\' for protection plan redeployment.', required=False)
    parser.add_argument('-r', '--redeploy', dest='redeploy', action='store_true', help='Force protection plan redeployment.\
    Can be used only with \'-pi\' option. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -r\'.', required=False)
    parser.add_argument('--check-plan-list', dest='check_plan_list', help='List all protection plans that match specified name.', required=False)

    # Option for both tenants and plans
    parser.add_argument('-fcp', '--fix-centralized-protection', dest='fix_centralized_protection', action='store_true', help='Create missing Gtob::Dto::CentralizedProtection object.\
    Can be used only with \'-pi\' and \'-ti\' options. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -fcp\'.', required=False)
    parser.add_argument('-o', '--change-owner', dest='change_owner', help='Change owner of Gtob::Dto::CentralizedProtection object.\
    Can be used only with \'-pi\' option. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -o 102665\'.', required=False)

    #common options
    parser.add_argument('-f', '--fix', dest='fix', action='store_true', help='Enable fix mode. Can be used with other options.', required=False)
    parser.add_argument('-d', '--delete', dest='delete', action='store_true', help='Enable delete mode (interactive). Can be used with other options.', required=False)
    parser.add_argument('-e', '--extra', dest='extra', action='store_true', help='Prints extra data. Can be used with other options.', required=False)

    #auto update
    parser.add_argument('-bu', '--build-url', dest='build_url', help='List auto update using build url', required=False)
    parser.add_argument('-ui', '--update-id', dest='update_id', help='List auto update using id', required=False)
    parser.add_argument('-lu', '--list-updates', dest='list_updates', action='store_true', help='List all auto updates', required=False)

    #misc options
    parser.add_argument('-p1', '--parameter1', dest='parameter1', help='Custom parameter that can be used with other options.', required=False)
    parser.add_argument('-p2', '--parameter2', dest='parameter2', help='Custom parameter that can be used with other options.', required=False)
    parser.add_argument('--fix-instances', dest='fix_instances', help='Check/Fix instances statuses for specified tenant or \'all\'. \
    It checks all recent activities for last 24 hours or time period specifid using \'p1\' and \'p2\' options.\
    Use \'--fix-instances all\' for all tenants.\
    Use \'-f\' for fixing. Use\'p1\' for specifing the amount of days and \'p2\' for hours.\
    Example: \'--fix-instances all -f -p1 2 -p2 13\'', required=False)
    parser.add_argument('--check-sync', dest='check_sync', help='Check/Fix sync statuses for machines of specified tenant or \'all\'. \
    It checks most recent activitiy on AMS side and most recent activity on MMS side. If theirs \'FinishTime\' isn\'t equal and \'-f\' specified \
    connection to MMS will be dropped and it will cause re-sync. Use \'p1\' with any value to drop connection to old agent (<=6.0).', required=False)
    parser.add_argument('--check-deprecated-vms', dest='check_deprecated_vms', action='store_true', help='Checks existence of deprecated virtual instances (aka Red hat KVM and etc).\
    Use \'-f\' to force deletion of deprecated instances.', required=False)
    parser.add_argument('-sm', '--sync-monitor', dest='sync_monitor', help='Show sync statistics. Show summary if \'summary\' is specified. Show all channels if \'all\' specified.\
    Show specific machine channels if machine ID is specified. Example: \'watch acropsh -m amsctl -sm E9A35C00-388F-4522-AD07-981139D6F9A3\'.', required=False)
    parser.add_argument('-ps', '--perf-stat', dest='perf_stat', action='store_true', help='Show aggregated DML performance statistics using data from \'SERVICE_HOME_DIR/perf_stat\' folder.', required=False)
    parser.add_argument('-ds', '--dml-stat', dest='dml_stat', action='store_true', help='Show aggregated DML performance statistics using data from \'SERVICE_HOME_DIR/perf_stat\' folder.', required=False)
    parser.add_argument('-od', '--obsolete-data', dest='obsolete_data', action='store_true', help='Analyze objects consistency. May take a lot of time if run for \'all\'.\
    Use \'-p1\' for checking objects of specific tenant . Use \'p2\' for specifing action: \'ip\' - only check item protections, \'protection\' or \'all\'.\
    Example: \'acropsh amsctl.py -od  -p1 92271\'.', required=False)
    parser.add_argument('-ai', '--agent-info', dest='agent_info', action='store_true', help='Collect agents statistics and outputs it as JSON.', required=False)
    parser.add_argument('-usi', '--user-info', dest='user_info', action='store_true', help='Collect info about users and outputs it as JSON.', required=False)
    parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', help='Disable output to stdout except result.', required=False)
    parser.add_argument('-c', '--count', dest='count', help='Count objects. Specify \'machine-statistics\' for machine statistics or \'backup-statistics\' for backup statistics, \
    otherwise tries to count objects assumes input as \'Is\' value.\
    Example: \'acropsh amsctl.py -c Gtob::Dto::ItemProtection\'.', required=False)
    parser.add_argument('-s', '--status', dest='check_status', action='store_true', help='Check AMS status.', required=False)
    parser.add_argument('-bn', '--backup-notifications', dest='backup_notifications', action='store_true', help='Show information about backup notifications', required=False)
    parser.add_argument('-do', '--delete-object', dest='delete_object', help='Delete object by ID', required=False)
    parser.add_argument('--fix-all-centralized-protections', dest='fix_all_centralized_protections', action='store_true', help='Create missing Gtob::Dto::CentralizedProtection objects \
    for all plans', required=False)
    parser.add_argument('--report-all-instances', dest='report_all_instances', action='store_true', help='Output information about all instances')
    parser.add_argument('--report-all-machines', dest='report_all_machines', action='store_true', help='Report offline machines')

    config = None
    try:
        config = parser.parse_arguments()
    except acrort.Exception as exception:
        error = exception.to_error()
        ret = error.to_exit_code()
        if ret == acrort.common.EXCEPTION_AWARE_RETURN_CODE:
            error.throw()
        return ret

    global args
    args = config['args']

    try:
        global Log
        if args.quiet:
            Log = acrobind.NullOutput()
        else:
            Log = acrobind.Output(config, end='\n')

        if args.perf_stat:
            perf_stat()
            return

        if args.dml_stat:
            dml_stat()
            return

        if args.backup_notifications:
            check_notifications()
            return

        if args.connection:
            hostname = args.connection[0]
            username = args.connection[1]
            password = args.connection[2]
            Log.write('Connecting to AMS \'{0}@{1}\' ...'.format(username, hostname), end='')
            connection_args = ('ams', hostname, (username, password))
        else:
            Log.write('Connecting to AMS locally...', end='')
            connection_args = ('ams', )

        if args.check_status:
            check_status(connection_args)
            return

        connection = acrort.connectivity.Connection(*connection_args, client_session_data={"identity_disabled": True})
        Log.write('done')

        if args.count:
            counter_mode(connection)
            return
        if args.list_updates or args.build_url or args.update_id:
            analyze_updates(connection)
            return
        if args.user_info:
            collect_users_info(connection)
            return
        if args.agent_info:
            collect_agents_statistics(connection)
            return
        if args.obsolete_data:
            obsolete_data(connection)
            return
        if args.report_all_machines:
            report_all_machines(connection)
            return
        if args.quotas_reconcile:
            quotas_reconcile(connection)
            return
        if args.drop_orphaned_machines:
            drop_orphaned_machines(connection)
            return
        if args.drop_orphaned_instances:
            drop_orphaned_instances(connection)
            return
        if args.tenant_id:
            check_tenant(connection)
            return
        if args.tenant_name:
            list_tenants(connection)
            return
        if args.check_plan_list:
            check_plan_list(connection)
            return
        if args.plan_name or args.plan_id:
            describe_plans(connection)
            return
        if args.machine_name:
            list_machines(connection)
            return
        if args.instance_name:
            list_instances(connection)
            return
        if args.fix_instances:
            fix_instances(connection)
            return
        if args.check_sync:
            check_sync(connection)
            return
        if args.sync_monitor:
            sync_monitor(connection)
            return
        if args.check_deprecated_vms:
            check_deprecated_vms(connection)
            return
        if args.delete_object:
            delete_object(connection, args)
            return
        if args.fix_all_centralized_protections:
            fix_all_centralized_protections(connection)
            return
        if args.report_all_instances:
            report_all_instances(connection)
            return
        if args.report_all_machines:
            report_all_machines(connection)
            return
        if args.instance_id:
            check_instance(connection)
        if args.machine_id:
            check_machine(connection)

    except Exception as e:
        error_log(format_backtrace(e))


if __name__ == '__main__':
    exit(acrobind.interruptable_safe_execute(main))