Your IP : 3.139.240.219


Current Path : /opt/webdir/lib/
Upload File :
Current File : //opt/webdir/lib/Pool.pm

# main class for manage in the ansible pool
#
package Pool;
use strict;
use warnings;
use Moose;
use File::Basename qw( dirname basename );
use File::Spec::Functions;
use File::Path qw(remove_tree);
use Data::Dumper;
use Sys::Hostname;
use bxNetwork;
use bxNetworkNode;
use bxDaemon;
use Output;
use bxInventory qw( get_from_yaml save_to_yaml);

# main ansible config dir, all hosts and groups file definitions saved here
has 'ansible_dir',  is => 'ro', default => '/etc/ansible';
has 'bitrix_dir',   is => 'ro', default => '/opt/webdir';
has 'ansible_conf', is => 'ro', lazy    => 1, builder => 'set_ansible_conf';
has 'bitrix_conf',  is => 'ro', lazy    => 1, builder => 'set_bitrix_conf';
has 'debug',        is => 'ro', lazy    => 1, default => 0;
has 'logfile',      is => 'ro', default => '/opt/webdir/logs/pool_manage.debug';
has 'bitrix_type',  is => 'ro', default => 'general';

sub esc_chars {
    my $str = shift;
    $str =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\ ])/\\$1/g;
    return $str;
}

# set default config directories
sub set_ansible_conf {
    my $self = shift;

    my $ansible_base = $self->ansible_dir;

    my $ansibleConfigOpt = {
        base        => $ansible_base,
        main        => catfile( $ansible_base, "ansible.cfg" ),
        hosts       => catfile( $ansible_base, "hosts" ),
        sshkeys     => catfile( $ansible_base, ".ssh" ),
        group_vars  => catfile( $ansible_base, "group_vars" ),
        host_vars   => catfile( $ansible_base, "host_vars" ),
        library     => catfile( $ansible_base, "library" ),
        playbook    => "/usr/bin/ansible-playbook",
        ansible     => "/usr/bin/ansible",
        client_conf => catfile( $ansible_base, "ansible-roles" ),
    };

    return $ansibleConfigOpt;
}

sub set_bitrix_conf {
    my $self = shift;

    my $bitrix_base     = $self->bitrix_dir;
    my $bitrixConfigOpt = {
        base           => $bitrix_base,
        logs           => catfile( $bitrix_base, 'logs' ),
        aHostsTemplate => catfile( $bitrix_base, 'templates', 'ansible' ),
        aHostsGroups   => [
            'hosts',     'mgmt',  'web',  'sphinx',
            'memcached', 'mysql', 'push', 'transformer'
        ],
        aHostsDefault => 'hosts',
        aHostsPrefix  => 'bitrix',
    };

    return $bitrixConfigOpt;
}

sub generate_random {
    my $self = shift;
    my $len  = shift;
    if ( not defined $len ) { $len = 10 }
    my @alphanum = ( 'A' .. 'Z', 'a' .. 'z', 0 .. 9 );
    my $random =
      join( '', map( $alphanum[ rand($#alphanum) ], ( 1 .. $len ) ) );
    return $random;
}

sub generate_host_id {
    my $self   = shift;
    my $tm     = time;
    my $random = $self->generate_random;
    return $tm . "_" . $random;
}

sub generate_host_password {
    my $self = shift;
    my $host = shift;
    return $host . "_" . $self->generate_random;
}

sub get_ansible_inventory {
    my ($self) = @_;

    my %ansible_inventory;

    my $ansible_conf = $self->ansible_conf;
    my $bitrix_conf  = $self->bitrix_conf;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    # get info from config file
    open( my $ch, '<', $ansible_conf->{'hosts'} )
      or return Output->new(
        'error'   => '1',
        'message' => "$message_p: Config file "
          . $ansible_conf->{'hosts'}
          . " does not exist"
      );
    my ( $section_name, $is_pool_group );
    my $server_cnt = 0;
    while (<$ch>) {
        chomp;
        next if (/^$/);
        next if (/^#/);
        s/^\s+//;
        s/\s+$//;

        # section found
        if (/^\[([^\]]+)\]/) {
            $section_name = $1;
            if ( $section_name =~ /^$bitrix_conf->{'aHostsPrefix'}\-(\S+)$/ ) {
                $section_name  = $1;
                $is_pool_group = 1;
            }
            else {
                $is_pool_group = 0;
            }
        }

        next if ( $is_pool_group == 0 );

        # option found
        if (/^([^\]\[\s]+)\s+(.+)$/) {
            my $server      = $1;             # vm1
            my $server_opt  = $2;             # ansible_ssh_host=192.168.1.231
            my $server_type = "ssh";          # default connection type
            my $server_ip   = "127.0.0.1";    # default IP address

         # [bitrix-hosts]
         # vm03.ksh.bx ansible_ssh_host=172.17.10.103
         # vm04.ksh.bx   ansible_connection=local ansible_ssh_host=172.17.10.104
            if ( $server_opt =~ /ansible_connection\s*=\s*(\S+)/ ) {
                $server_type = $1;
            }
            if ( $server_opt =~ /ansible_ssh_host\s*=\s*(\S+)/ ) {
                $server_ip = $1;
            }

            # get host connection settings from hosts group
            # and personal settings from host_vars
            if ( $section_name =~ /^$bitrix_conf->{'aHostsDefault'}$/ ) {
                $ansible_inventory{$server} = {
                    'ip'         => $server_ip,
                    'connection' => $server_type,
                    'roles'      => {}
                };

                my $server_file =
                  catfile( $ansible_conf->{'host_vars'}, $server );
                my $get_host_vars = get_from_yaml($server_file);
                if ( $get_host_vars->is_error ) {

                    #return $get_host_vars;
                    $ansible_inventory{$server}->{host_vars}      = {};
                    $ansible_inventory{$server}->{hostname}       = $server;
                    $ansible_inventory{$server}->{host_vars_file} = "";
                    next;
                }

                $server_cnt++;
                my $host_vars = $get_host_vars->data->[1];
                $ansible_inventory{$server}->{host_vars}      = $host_vars;
                $ansible_inventory{$server}->{host_vars_file} = $server_file;
                $ansible_inventory{$server}->{hostname} =
                  ( $host_vars->{bx_host} ) ? $host_vars->{bx_host} : $server;
                $ansible_inventory{$server}->{host_id} = $host_vars->{host_id};
                if (   ( defined $host_vars->{bx_host} )
                    && ( $host_vars->{bx_host} ne $server ) )
                {
                    $ansible_inventory{aliases}->{ $host_vars->{bx_host} } =
                      $server;
                }

            }
            elsif ( $section_name =~
                /^(mysql|memcached|sphinx|push|web|mgmt|transformer)$/ )
            {
                my $group = $1;
                $ansible_inventory{$server}->{groups}->{$group} = 1;
            }
        }
    }
    close $ch;
    if ( $server_cnt == 0 ) {
        return Output->new(
            error   => 2,
            message => "$message_p: Not found records in ansible config "
              . $ansible_conf->{'hosts'},
        );
    }

    foreach my $server ( keys %ansible_inventory ) {
        next if ( $server eq "aliases" );

        #print Dumper($ansible_inventory{$server}->{host_vars});

        #if (grep (/^mysql$/, @{$ansible_inventory->{$server}->{groups}} )){
        if ( exists $ansible_inventory{$server}->{groups}->{mysql} ) {
            $ansible_inventory{$server}->{roles}->{mysql} = {
                type => (
                    $ansible_inventory{$server}->{host_vars}
                      ->{mysql_replication_role}
                  )
                ? $ansible_inventory{$server}->{host_vars}
                  ->{mysql_replication_role}
                : "slave",
                id => (
                    $ansible_inventory{$server}->{host_vars}->{mysql_serverid}
                  ) ? $ansible_inventory{$server}->{host_vars}->{mysql_serverid}
                : 1,
            };
        }

        #if (grep (/^memcached$/, @{$ansible_inventory->{$server}->{groups}} )){
        if ( exists $ansible_inventory{$server}->{groups}->{memcached} ) {

            if ( $ansible_inventory{$server}->{host_vars}->{memcached_socket} )
            {
                $ansible_inventory{$server}->{roles}->{memcached}
                  ->{memcached_socket} =
                  $ansible_inventory{$server}->{host_vars}->{memcached_socket};
            }
            else {
                $ansible_inventory{$server}->{roles}->{memcached}
                  ->{memcached_port} =
                  ( $ansible_inventory{$server}->{host_vars}->{memcached_port} )
                  ? $ansible_inventory{$server}->{host_vars}->{memcached_port}
                  : 11211;
            }
            $ansible_inventory{$server}->{roles}->{memcached}->{memcached_size}
              =
              ( $ansible_inventory{$server}->{host_vars}->{memcached_size} )
              ? $ansible_inventory{$server}->{host_vars}->{memcached_size}
              : 64;
        }

        # searchd
        if ( exists $ansible_inventory{$server}->{groups}->{sphinx} ) {

            $ansible_inventory{$server}->{roles}->{sphinx} = {
                sphinx_general_listen => (
                    $ansible_inventory{$server}->{host_vars}
                      ->{sphinx_general_listen}
                  )
                ? $ansible_inventory{$server}->{host_vars}
                  ->{sphinx_general_listen}
                : 9312,
                sphinx_mysqlproto_listen => (
                    $ansible_inventory{$server}->{host_vars}
                      ->{sphinx_mysqlproto_listen}
                  )
                ? $ansible_inventory{$server}->{host_vars}
                  ->{sphinx_mysqlproto_listen}
                : 9306,
            };
        }

        if ( exists $ansible_inventory{$server}->{groups}->{web} ) {
            $ansible_inventory{$server}->{roles}->{web} = {};
        }
        if ( exists $ansible_inventory{$server}->{groups}->{push} ) {
            $ansible_inventory{$server}->{roles}->{push} = {};
        }
        if ( exists $ansible_inventory{$server}->{groups}->{mgmt} ) {
            $ansible_inventory{$server}->{roles}->{mgmt} = {};
        }
        if ( exists $ansible_inventory{$server}->{groups}->{transformer} ) {


            #$ansible_inventory{$server}->{roles}->{transformer} = {};
            $ansible_inventory{$server}->{roles}->{transformer} = {
                transformer_dir => (
                    $ansible_inventory{$server}->{host_vars}->{transformer_dir}
                  )
                ? $ansible_inventory{$server}->{host_vars}->{transformer_dir}
                : "",
                transformer_site => (
                    $ansible_inventory{$server}->{host_vars}->{transformer_site}
                  )
                ? $ansible_inventory{$server}->{host_vars}->{transformer_site}
                : "",


            };
        }

    }

    return Output->new(
        error => 0,
        data  => [ "inventory", \%ansible_inventory ],
    );
}

# get information about current configuration of ansible pool
sub get_ansible_data {
    my ( $self, $host ) = @_;

    # initilize data
    my $ansible_conf = $self->ansible_conf;
    my $bitrix_conf  = $self->bitrix_conf;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    my $get_ansible_inventory = $self->get_ansible_inventory();
    return $get_ansible_inventory if ( $get_ansible_inventory->is_error );

    my $ansible_pool_data = $get_ansible_inventory->data->[1];
    delete $ansible_pool_data->{aliases}
      if ( exists $ansible_pool_data->{aliases} );
    if ( defined $host ) {
        foreach my $s ( keys %$ansible_pool_data ) {
            next if ( $host eq $s );
            next if ( $host eq $ansible_pool_data->{$s}->{ip} );
            next if ( $host eq $ansible_pool_data->{$s}->{hostname} );
            delete $ansible_pool_data->{$s};
        }
    }

    return Output->new(
        'error' => 0,
        data    => [ 'hosts', $ansible_pool_data ]
    );

}

sub get_inventory_hostname {
    my ( $self, $host ) = @_;

    if ( not defined $host ) {
        return Output->new(
            error   => 1,
            message => "Option host is mandatory option",
        );
    }

    # initilize data
    my $ansible_conf = $self->ansible_conf;
    my $bitrix_conf  = $self->bitrix_conf;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    my $get_ansible_inventory = $self->get_ansible_inventory();
    return $get_ansible_inventory if ( $get_ansible_inventory->is_error );

    my $ansible_pool_data = $get_ansible_inventory->data->[1];
    delete $ansible_pool_data->{aliases}
      if ( exists $ansible_pool_data->{aliases} );
    if ( defined $host ) {
        foreach my $s ( keys %$ansible_pool_data ) {
            if (   ( $host eq $s )
                || ( $host eq $ansible_pool_data->{$s}->{ip} )
                || ( $host eq $ansible_pool_data->{$s}->{hostname} ) )
            {
                return Output->new(
                    error => 0,
                    data  => [ 'ident', $s ],
                );
            }
        }
    }

    return Output->new(
        'error'   => 1,
        'message' => "Cannot find host=$host in the pool."
    );

}

sub get_inventory_hostname_at_group {
    my ( $self, $group ) = @_;

    if ( not defined $group ) {
        return Output->new(
            error   => 1,
            message => "Option group is mandatory option",
        );
    }

    # initilize data
    my $ansible_conf = $self->ansible_conf;
    my $bitrix_conf  = $self->bitrix_conf;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    my $get_ansible_inventory = $self->get_ansible_inventory();
    return $get_ansible_inventory if ( $get_ansible_inventory->is_error );

    my $ansible_pool_data = $get_ansible_inventory->data->[1];
    delete $ansible_pool_data->{aliases}
      if ( exists $ansible_pool_data->{aliases} );
    foreach my $s ( keys %$ansible_pool_data ) {
        next if ( exists $ansible_pool_data->{$s}->{roles}->{$group} );
        delete $ansible_pool_data->{$s};
    }

    return Output->new(
        'error' => 0,
        data    => [ 'hosts', $ansible_pool_data ]
    );

}

# create config file from template
# usage on initial setup
# replace only IP address and HostName by local values
sub create_conf_from_template {
    my $template = shift;    # template file
    my $dest     = shift;    # destination file
    my $master_info =
      shift;    # hash with master info{ netaddr, host, interface, netname }
    my $bitrix_type = shift;
    if ( not defined $bitrix_type ) {
        $bitrix_type = "general";
    }

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $po        = Pool->new();
    my $debug     = $po->debug;
    my $logOutput = Output->new( error => 0, logfile => $po->logfile );

    if ( $bitrix_type ne "general" ) {
        if ( -f $template . "_" . $bitrix_type ) {
            $template .= "_" . $bitrix_type;
        }
    }

    my $netaddr_type = 'ipv4';
    if ( $master_info->{'netaddr'} !~ /^[\d\.]+$/ ) { $netaddr_type = 'fqdn'; }

    # if template not exists - nothing to do
    ( -f $template )
      or return Output->new(
        error   => 0,
        message => "$message_p: not found template. nothing to do!"
      );

    # replace
    open( my $th, '<', "$template" )
      or return Output->new(
        error   => 1,
        message => "$message_p: cannot open $template: $!"
      );
    open( my $dh, '>', "$dest" )
      or return Output->new(
        error   => 1,
        message => "$message_p: cannot open $dest: $!"
      );
    while (<$th>) {
        s/\{\{\s*hostname\s*\}\}/$master_info->{'host'}/g;
        s/\{\{\s*host_ip_address\s*\}\}/$master_info->{'netaddr'}/g;
        s/\{\{\s*host_id\s*\}\}/$master_info->{'host_id'}/g;
        s/\{\{\s*host_pass\s*\}\}/$master_info->{'host_pass'}/g;
        s/\{\{\s*local_interface\s*\}\}/$master_info->{'interface'}/g;
        s/\{\{\s*netaddr_type\s*\}\}/$netaddr_type/g;
        s/\{\{\s*host_netname\s*\}\}/$master_info->{'netname'}/g;

        print $dh $_;
    }

    close $th;
    close $dh;

    # set permission
    chmod 0640, $dest;

    return Output->new(
        error   => 0,
        message => "$message_p: replace in $dest complete"
    );
}

# test ansible clien file
sub test_ansible_client_file {
    my $self = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    # get ansible config options
    my $ansible_conf = $self->ansible_conf;

    # test if host already in the pool
    if ( -f $ansible_conf->{'client_conf'} ) {
        if ($debug) {
            $logOutput->log_data( "$message_p: Found client config "
                  . $ansible_conf->{'client_conf'} );
        }

        open( my $ch, '<', $ansible_conf->{'client_conf'} )
          or return Output->new(
            error   => 2,
            message => "$message_p: Found client "
              . $ansible_conf->{'client_conf'}
              . ", cannot open it: $!",
          );

        my $local_name   = undef;
        my @local_groups = ();
        while (<$ch>) {
            s/^\s+//;
            s/\s\+$//;
            next if (/^#/);
            next if (/^$/);

            if (/^hostname\s*=\s*(\S+)$/) {
                $local_name = $1;
            }

            if (/^groups\s*=(.+)$/) {
                my $groups = $1;
                $groups =~ s/^\s+//;
                $groups =~ s/\s+$//;
                @local_groups = split( /\s+/, $groups );
            }
        }
        close $ch;

        if ( defined $local_name ) {
            if ( grep ( /^bitrix-mgmt$/, @local_groups ) ) {
                return Output->new(
                    error => 1,
                    message =>
                      "$message_p: Bitrix pool already exists in hosts file"
                );
            }
            else {
                return Output->new(
                    error => 1,
                    message =>
                      "$message_p: Host $local_name is configured as a client"
                );
            }
        }
        else {
            return Output->new(
                error   => 0,
                message => "$message_p: file "
                  . $ansible_conf->{'client_conf'}
                  . "doesn't contain pool info",
            );
        }
    }
    else {
        if ($debug) {
            $logOutput->log_data( "$message_p: not found config client "
                  . $ansible_conf->{'client_conf'} );
        }
        return Output->new(
            error   => 0,
            message => "$message_p: not found config client "
              . $ansible_conf->{'client_conf'},
        );
    }
}

# create pool ssh keys and save it for local usage in root directory
sub create_pool_ssh_keys {
    my $self     = shift;
    my $hostname = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $confData  = $self->get_ansible_data();
    my $debug     = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    $hostname = Pool::esc_chars($hostname);

    # create directory for ssh keys: /etc/ansible/.ssh
    my $ansible_conf = $self->ansible_conf;

    if ( !-d $ansible_conf->{'sshkeys'} ) {
        mkdir $ansible_conf->{'sshkeys'}
          or return Output->new(
            error   => 2,
            message => "$message_p: Cannot create directory "
              . $ansible_conf->{'sshkeys'}
          );
        chmod 0700, $ansible_conf->{'sshkeys'};
        if ($debug) { $logOutput->log_data("$message_p: create ssh key dir"); }
    }

    # create ssh key file
    my $random     = $self->generate_random;
    my $sshkey_sec = catfile( $ansible_conf->{'sshkeys'}, "$random.bxkey" );
    my $sshkey_pub = catfile( $ansible_conf->{'sshkeys'}, "$random.bxkey.pub" );
    if ( -f $sshkey_sec ) { unlink $sshkey_sec; }
    if ( -f $sshkey_pub ) { unlink $sshkey_pub; }
    if ($debug) { $logOutput->log_data("$message_p: defined ssh key path"); }

    # ssh generate via ssh-keygen (system):
    my $ssh_keygen_cmd =
qq|ssh-keygen -t rsa -N "" -f $sshkey_sec -C "ANSIBLE_KEY_$hostname" >/dev/null 2>&1|;
    system($ssh_keygen_cmd ) == 0
      or return Output->new(
        error   => 3,
        message => "$message_p: Cannot generate sshkey for bitrix pool."
      );
    if ($debug) {
        $logOutput->log_data("$message_p: generate key $sshkey_sec");
    }

    # save public key info to variable
    open( my $sp, '<', $sshkey_pub )
      or return Output->new(
        error   => 1,
        message => "$message_p: cannot open $sshkey_pub: $!",
      );
    my $key_info = "";
    while (<$sp>) { $key_info .= $_; }
    close $sp;

    # install key to local server, on current server
    my $ssh_dir = "/root/.ssh";
    if ( !-d $ssh_dir ) {
        mkdir $ssh_dir;
        chmod 0700, $ssh_dir;
    }

    my $ssh_auth = catfile( $ssh_dir, "authorized_keys" );
    open( my $sa, '>>', $ssh_auth )
      or return Output->new(
        error   => 1,
        message => "$message_p: cannot open $ssh_auth: $!",
      );
    print $sa $key_info;
    close $sa;
    if ($debug) {
        $logOutput->log_data(
            "$message_p: update $ssh_auth by new key $sshkey_pub on localhost");
    }

    return Output->new(
        error => 0,
        data  => [ 'sshkey', $sshkey_sec, $sshkey_pub ],
    );
}

# save ssh security key to ansible.cfg and set display_skipped_hosts to False
sub update_ansible_main_config {
    my $self       = shift;
    my $sshkey_sec = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $confData  = $self->get_ansible_data();
    my $debug     = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    my $display_skipped_hosts = 'False';

    # replace current ansible private key by new one
    # /etc/ansible/ansible.cfg
    my $ansible_conf = $self->ansible_conf;
    my $work_conf    = $ansible_conf->{'main'};
    my $temp_conf    = $ansible_conf->{'main'} . ".tmp";

    open( my $tmph, '>', $temp_conf )
      or return Output->new(
        error   => 4,
        message => "$message_p: Cannot open temporary $temp_conf: $!"
      );
    open( my $workh, '<', $work_conf )
      or return Output->new(
        error   => 4,
        message => "$message_p: Cannot open config $work_conf: $!"
      );

    my %updates = (
        private_key_file      => [ $sshkey_sec,            0 ],
        display_skipped_hosts => [ $display_skipped_hosts, 0 ],
    );

    my $new_key_is_set               = 0;
    my $display_skipped_hosts_is_set = 0;
    while (<$workh>) {
        chomp;
        my $line = $_;
        if ( $line =~ /^#?\s*(\S+)\s*=\s+(\S+)/ ) {
            my $config_key   = $1;
            my $config_value = $2;
            if ( grep /^$config_key$/, keys %updates ) {
                $updates{$config_key}->[1] = 1;
                $line = "$config_key = " . $updates{$config_key}->[0] . "\n";

                if ($debug) {
                    $logOutput->log_data(
"$message_p: replace $config_key, was $config_value set to "
                          . $updates{$config_key}->[0] );
                }
            }
        }
        print $tmph $line, "\n";
    }
    close $tmph;
    close $workh;

    # test if all updates complete
    foreach my $key ( keys %updates ) {
        if ( $updates{$key}->[1] == 0 ) {
            return Output->new(
                error   => 5,
                message => "Cannot replace $key value in $work_conf",
            );
        }
    }

    # delete old config, recreate new one
    unlink $work_conf;
    rename $temp_conf,
      $work_conf
      or return Output->new(
        error   => 6,
        message => "$message_p: Cannot replace work config $work_conf"
      );

    if ($debug) {
        $logOutput->log_data(
            "$message_p: update $work_conf by new private_key_file");
    }

    return Output->new(
        error   => 0,
        message => "update $work_conf",
        data    => [ 'updated', $work_conf ],
    );
}

sub forget_host {
    my ( $self, $host ) = @_;
    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $logOutput = Output->new(
        error   => 0,
        logfile => $self->logfile,
        debug   => $self->debug
    );
    my $get_host_ident = $self->get_inventory_hostname($host);
    return $get_host_ident if ( $get_host_ident->is_error );

    my $host_ident = $get_host_ident->data->[1];

    $logOutput->log_data(
        "$message_p: delete server=$host_ident from the config files");

    my $host_file  = catfile( $self->ansible_conf->{'host_vars'}, $host_ident );
    my $hosts_file = $self->{ansible_conf}->{'hosts'};
    my $parse_yaml = get_from_yaml($host_file);
    return $parse_yaml if ( $parse_yaml->is_error );
    my $f_opts = {
        common_manage      => 'forget',
        forget_bx_hostname => $parse_yaml->data->[1]->{bx_hostname},
        forget_bx_netaddr  => $parse_yaml->data->[1]->{bx_netaddr},
        forget_bx_host     => ( $parse_yaml->data->[1]->{bx_host} )
        ? $parse_yaml->data->[1]->{bx_host}
        : $parse_yaml->data->[1]->{bx_hostname},
    };

    my %w_objs;

    # delete records from inventory hosts file
    my $tmp_hosts_file = $hosts_file . ".tmp";
    open( my $hf, '<', $hosts_file )
      or return Output->new(
        error   => 1,
        message => "Cannot open $hosts_file:$!",
      );
    open( my $thf, '>', $tmp_hosts_file )
      or return Output->new(
        error   => 1,
        message => "Cannot open $tmp_hosts_file:$!",
      );
    my $deleted_str = 0;
    while ( my $line = <$hf> ) {
        chomp($line);
        $line =~ s/^\s+//;
        $line =~ s/\s+$//;
        if ( $line =~ /^$host_ident\s+/ ) {
            $deleted_str++;
            next;
        }
        print $thf "$line\n";
    }
    close $thf;
    close $hf;
    if ($deleted_str) {
        unlink $hosts_file;
        rename $tmp_hosts_file, $hosts_file;
    }
    else {
        $w_objs{$hosts_file} = "Not found record  $host_ident in file";
    }

    # delete host_vars file
    unlink $host_file or $w_objs{$host_file} = "$!";

    # run ansible common script (update iptables and othe configs)
    my $cmd_play = $self->ansible_conf->{'playbook'};
    my $cmd_conf = catfile( $self->ansible_conf->{'base'}, "common.yml" );

    # run as daemon in background
    my $dh = bxDaemon->new( task_cmd => qq($cmd_play  $cmd_conf) );
    my $created_process = $dh->startAnsibleProcess( 'common', $f_opts );

    return Output->new(
        error   => 0,
        message => "Deleting $host_ident configuration files is completed",
        data    => [ $message_p, { 'warnings' => \%w_objs } ]
    );
}

sub change_hostname {
    my ( $self, $host, $hostname ) = @_;
    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $logOutput = Output->new(
        error   => 0,
        logfile => $self->logfile,
        debug   => $self->debug
    );
    my $get_host_ident = $self->get_inventory_hostname($host);
    return $get_host_ident if ( $get_host_ident->is_error );

    my $host_ident = $get_host_ident->data->[1];

    $logOutput->log_data(
"$message_p: change hostname for server=$host_ident from the config files"
    );

    my $host_file = catfile( $self->ansible_conf->{'host_vars'}, $host_ident );
    my $get_host_info = get_from_yaml($host_file);
    if ( $get_host_info->is_error ) {
        return $get_host_info;
    }
    my $host_info = $get_host_info->data->[1];
    $host_info->{bx_host} = $hostname;
    my $save_host_info = save_to_yaml( $host_info, $host_file );
    if ( $save_host_info->is_error ) {
        return $save_host_info;
    }

    # run ansible common script (update iptables and othe configs)
    my $cmd_play = $self->ansible_conf->{'playbook'};
    my $cmd_conf = catfile( $self->ansible_conf->{'base'}, "common.yml" );

    # run as daemon in background
    my $dh = bxDaemon->new( task_cmd => qq($cmd_play  $cmd_conf) );
    my $created_process = $dh->startProcess('common');

    return $created_process;
}

# delete pool
sub delete_pool {
    my $self = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $logOutput = Output->new(
        error   => 0,
        logfile => $self->logfile,
        debug   => $self->debug
    );

    $logOutput->log_data("$message_p: delete config files for pool");

    # run ansible common script (update iptables and othe configs)
    my $cmd_play = $self->ansible_conf->{'playbook'};
    my $cmd_conf = catfile( $self->ansible_conf->{'base'}, "delete_pool.yml" );

    # run as daemon in background
    my $dh = bxDaemon->new( task_cmd => qq($cmd_play  $cmd_conf) );
    my $created_process = $dh->startProcess('delete_pool');

    return $created_process;
}

# create new pool with default ansible configuration:
# create ssh-key and groups definition in config file
sub create_new_pool {
    my ( $self, $req_hostname, $req_interface, $req_ip ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $confData    = $self->get_ansible_data();
    my $debug       = $self->debug;
    my $bitrix_type = $self->bitrix_type;

    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

# create pool options for first node
#print "Input options: req_hostname=$req_hostname req_interface=$req_interface\n";
    if ( not defined $req_hostname )  { $req_hostname  = hostname; }
    if ( not defined $req_interface ) { $req_interface = 'any'; }
    if ( not defined $req_ip )        { $req_ip        = 'any'; }
    my $net = bxNetworkNode->new(
        manager_interface => $req_interface,
        manager_hostname  => $req_hostname,
        manager_ipaddress => $req_ip,
        debug             => $debug,
    );
    my $net_option = $net->create_network_options();
    if ( $net_option->is_error ) { return $net_option; }
    my $master_data = $net_option->get_data->[1];
    my $host_id     = $self->generate_host_id;
    my $host_pass   = $self->generate_host_password( $master_data->{'ident'} );

    my %master_host = (
        host      => $master_data->{'ident'},
        netaddr   => $master_data->{'netaddr'},
        interface => $master_data->{'interface'},
        netname   => $master_data->{'fqdn'},
        host_id   => $host_id,
        host_pass => $host_pass,
    );

    if ($debug) {
        $logOutput->log_data(
                "$message_p: start create pool; host="
              . $master_host{'host'}
              . " netaddr="
              . $master_host{'netaddr'} . " int="
              . $master_host{'interface'},
        );
    }

    # pool already exists
    if ( $confData->is_error == 0 ) {
        return Output->new(
            error   => 1,
            message => "$message_p: Bitrix pool already exists in hosts file"
        );
    }

    # test client config exist
    my $test_client = $self->test_ansible_client_file;
    if ( $test_client->is_error ) { return $test_client; }

    # create ssh keys
    my $get_sshkeys = $self->create_pool_ssh_keys( $master_host{'host'} );
    if ( $get_sshkeys->is_error ) { return $get_sshkeys; }
    my $sshkey_sec = $get_sshkeys->get_data->[1];

    # update ansible config
    my $update_main_conf = $self->update_ansible_main_config($sshkey_sec);
    if ( $update_main_conf->is_error ) { return $update_main_conf; }
    my $work_conf = $update_main_conf->get_data->[1];

    ### fill out hosts and group information with default data
    my $ansible_conf = $self->ansible_conf;

    # update host interface if subinterface found
    $master_host{'interface'} =~ s/^([^:]+):.+$/$1/;

    # hosts file
    my $hosts_template =
      catfile( $self->bitrix_conf->{'aHostsTemplate'}, "hosts" );
    my $hosts_dest = $ansible_conf->{'hosts'};
    my $replace_hosts =
      create_conf_from_template( $hosts_template, $hosts_dest, \%master_host,
        $bitrix_type );
    if ( $replace_hosts->is_error ) { return $replace_hosts; }
    if ($debug) {
        $logOutput->log_data("$message_p: create new config $hosts_dest");
    }

    # group_vars
    my $roles          = $self->bitrix_conf->{'aHostsGroups'};
    my $template_dir   = $self->bitrix_conf->{'aHostsTemplate'};
    my $prefix         = $self->bitrix_conf->{'aHostsPrefix'};
    my $group_vars_dir = $self->ansible_conf->{'group_vars'};
    if ( !-d $group_vars_dir ) { mkdir $group_vars_dir, 0750 }
    foreach my $group_name (@$roles) {
        my $group_template =
          catfile( $template_dir, $prefix . "-" . $group_name );
        my $group_dest =
          catfile( $group_vars_dir, $prefix . "-" . $group_name . ".yml" );
        my $replace_groups = create_conf_from_template( $group_template,
            $group_dest, \%master_host, $bitrix_type );
        if ( $replace_groups->is_error ) { return $replace_groups; }
        if ($debug) {
            $logOutput->log_data(
"$message_p: create new config $group_dest; with group default options"
            );
        }
    }

    # host_vars
    my $host_vars_dir = $self->ansible_conf->{'host_vars'};
    my $host_template = catfile( $template_dir, 'localhost' );
    my $host_dest     = catfile( $host_vars_dir, $master_host{'host'} );
    if ( !-d $host_vars_dir ) { mkdir $host_vars_dir, 0750 }
    my $replace_host =
      create_conf_from_template( $host_template, $host_dest, \%master_host,
        $bitrix_type );
    if ($debug) {
        $logOutput->log_data(
            "$message_p: create config $host_dest; deafult master host settings"
        );
    }

    if ( $replace_host->is_error ) { return $replace_host; }

    # run common playbook
    # set network, time
    # run monitor playbook with new option setings
    my $cmd_play = $ansible_conf->{'playbook'};
    my $cmd_conf = catfile( $ansible_conf->{'base'}, "common.yml" );

    # run as daemon in background
    my $dh = bxDaemon->new( task_cmd => qq($cmd_play  $cmd_conf) );
    my $created_process = $dh->startProcess('common');
    my ($task_id) =
      grep { !/^task_name$/ } keys %{ $created_process->{data}->[1] };

    my $task_pid    = $created_process->{$task_id}->{pid};
    my $task_status = $created_process->{$task_id}->{status};

    if ($debug) {
        $logOutput->log_data(
            "$message_p: configure common options on master server "
              . $master_host{'host'} );
    }

    # output info
    my $output_message = "Created manager configuration for";
    $output_message .= " identifier=" . $master_host{'host'};
    $output_message .= " interface=" . $master_host{'interface'};
    $output_message .= " netaddress=" . $master_host{'netaddr'} . "\\n";
    $output_message .= "Created sshkey - $sshkey_sec\\n";
    $output_message .= "Update config file $work_conf\\n";
    $output_message .= "Created pool configuration in $hosts_dest\\n";
    $output_message .= "Run configuration pool job task_id=$task_id\\n";
    $output_message .= "All operations complete\\n";

    return Output->new(
        error   => 0,
        message => $output_message,
        data    => [ "sshkey", "$sshkey_sec" ]
    );
}

# get path to ssh key
sub get_ssh_key {
    my $self = shift;

    my $message_p = "BX_KEY_VIEW";

    my $ansData     = $self->ansible_conf;
    my $ansMainConf = $ansData->{'main'};

    # search key in the config file
    open( my $ch, '<', $ansMainConf )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open main config $ansMainConf: $!"
      );
    my $ansSshKey = "";
    while (<$ch>) {
        if (/private_key_file\s*=\s*(\S+)/) { $ansSshKey = $1; }
    }
    close $ch;

    if ( !$ansSshKey ) {
        return Output->new(
            error => 2,
            message =>
"$message_p: Not found private_key_file derictive in config $ansMainConf"
        );
    }

    my $ansSshKeyPub = $ansSshKey . ".pub";
    if ( !-f $ansSshKey ) {
        return Output->new(
            error => 3,
            message =>
"$message_p: Record private_key_file found in the config, but private key does not exist in FS"
        );
    }
    if ( !-f $ansSshKeyPub ) {
        return Output->new(
            error => 3,
            message =>
"$message_p: Record private_key_file found in the config, but public key does not exist in FS"
        );
    }

    return Output->new( data => [ 'sshkey', $ansSshKey ] );
}

## update group information
# updated files in /etc/ansible/group_vars
# options = { group => groupname, opt1 => val1, opt2 => undef }
# opt2 - will be deleted, opt1 - updated|added
sub update_group_vars {
    my ( $self, $options ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;
    my $debug     = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    my $bxData  = $self->bitrix_conf;
    my $ansData = $self->ansible_conf;

    #print "bxPool: ".print Dumper($options);
    if ( not defined $options->{"group"} ) {
        return Output->new(
            error   => 1,
            message => "$message_p: group name is mandatory option"
        );
    }

    my $group = $options->{'group'};
    $group =~ s/^["']//;
    $group =~ s/['"]$//;

    my $group_vars_path = catfile( $ansData->{'group_vars'},
        $bxData->{'aHostsPrefix'} . '-' . $group . '.yml' );
    my $group_vars_temp = $group_vars_path . ".tmp";
    if ( !-f $group_vars_path ) {
        return Output->new(
            error   => 1,
            message => "$message_p: not found group_vars in $group_vars_path"
        );
    }
    $logOutput->log_data(
"$message_p: start update inventory group=$group inventory file=$group_vars_path"
    );

    # get current data from yaml
    my $get_inventory = get_from_yaml($group_vars_path);
    if ( $get_inventory->is_error ) {
        return $get_inventory;
    }
    my $inventory_data = $get_inventory->data->[1];

    #print Dumper($inventory_data);

    # create conf data for update
    my $updates      = 0;
    my $deletes      = 0;
    my $made_updates = 0;
    my $made_deletes = 0;
    foreach my $key ( keys %$options ) {

        # skip handler options
        next if ( $key =~ /^(group|state)$/ );

        # convert password_file to value which will be saved in the inventory
        if ( $key =~ /^(\S+_password)_file$/ ) {
            my $inventory_key = $1;
            open( my $h, '<', $options->{$key} )
              or return Output->new(
                error   => 1,
                message => "$message_p: cannot open file=" . $options->{$key},
              );
            my $value = <$h>;
            chomp($value);
            close $h;

            if (   ( defined $inventory_data->{$inventory_key} )
                && ( $inventory_data->{$inventory_key} eq $value ) )
            {
                next;
            }

            $inventory_data->{$inventory_key} = $value;
            $updates++;
        }
        else {
            # key=value
            if ( defined $options->{$key} ) {
                if ( defined $inventory_data->{$key} ) {
                    if ( $inventory_data->{$key} ne $options->{$key} ) {
                        $inventory_data->{$key} = $options->{$key};
                        $updates++;
                    }
                }
                else {
                    $inventory_data->{$key} = $options->{$key};
                    $updates++;
                }
            }

            # key
            else {
                if ( defined $inventory_data->{$key} ) {
                    delete $inventory_data->{$key};
                    $deletes++;
                }
            }

        }

    }

    $logOutput->log_data("$message_p: found updates=$updates deletes=$deletes");

    # update values or create new
    my $save_inventory = save_to_yaml( $inventory_data, $group_vars_temp );
    if ( $save_inventory->is_error ) {
        return $save_inventory;
    }

    # rewrite origin file
    unlink $group_vars_path;
    rename $group_vars_temp, $group_vars_path;
    chmod 0640, $group_vars_path;

    return Output->new(
        error   => 0,
        message => "$message_p: "
          . "File=$group_vars_path is modified; updates=$updates deletes=$deletes",
        data => [
            "$message_p",
            {
                updates => $updates,
                deletes => $deletes,
                file    => $group_vars_path
            }
        ]
    );
}

# get current status of monitoring; enable or disable
sub monitorStatus {
    my $self = shift;

    my $message_p = "BX_MONITOR";

    my $ansData = $self->ansible_conf;
    my $bxData  = $self->bitrix_conf;

    #print "ansData => ",Dumper($ansData);
    #print "bxData => ",Dumper($bxData);
    #
    my $monitoring_opt = {
        'monitoring_status'         => '',
        'monitoring_server'         => '',
        'monitoring_file'           => 0,
        'monitoring_server_netaddr' => '',
    };

# config file that contains monitoring information: /etc/ansible/group_vars/bitrix-hosts
    my $cfg_file = catfile( $ansData->{'group_vars'},
        $bxData->{'aHostsPrefix'} . '-' . $bxData->{'aHostsDefault'} . '.yml' );
    if ( !-f $cfg_file ) {
        return Output->new(
            error => 0,
            data  => [ 'monitor', $monitoring_opt ]
        );
    }

    open( my $ch, '<', $cfg_file )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open $cfg_file"
      );

    $monitoring_opt->{'monitoring_file'} = $cfg_file;
    while (<$ch>) {
        chomp;
        s/^\s+//;
        s/\s+$//;
        next if (/^#/);
        next if (/^$/);

        if (
/^(monitoring_status|monitoring_server|monitoring_server_netaddr)\s*:\s*(\S+)$/
          )
        {
            $monitoring_opt->{$1} = $2;
        }
    }

    close $ch;

    # if status wanted
    return Output->new( error => 0, data => [ 'monitor', $monitoring_opt ] );
}

# change config file for monitoring
# run playbook monitor.yml
sub monitorEnable {
    my ( $self, $opts ) = @_;

    my $ansData = $self->ansible_conf;

    # run monitor playbook with new option setings
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "monitor.yml" );
    my $cmd_opts = { 'monitoring_status' => 'enable' };

    # update ansible playbook options by optionals
    foreach my $o ( keys %$opts ) {
        if ( defined $opts->{$o} ) {
            $cmd_opts->{$o} = $opts->{$o};
        }
    }

    # run as daemon in background
    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'monitor', $cmd_opts );

    return $created_process;
}

# update monitoring configuration for new host
sub monitorUpdate {
    my $self = shift;

    # get current status of monitoring
    my $monitor = $self->monitorStatus;
    if ( $monitor->is_error ) {
        return Output->new( error => 1, message => "Cannot read config file" );
    }
    my $monitor_status = $monitor->get_data;

    # current monitoring options
    my $mon_file = $monitor_status->[1]->{'monitoring_file'};
    my $mon_flag = $monitor_status->[1]->{'monitoring_status'};
    my $mon_mgmt = $monitor_status->[1]->{'monitoring_server'};

    my $ansData = $self->ansible_conf;
    my $bxData  = $self->bitrix_conf;

    # existen or not file with default value for group bitrix-hosts
    if ( !$mon_file ) {
        return Output->new(
            error   => 1,
            message => "Monitoring does not enable"
        );
    }

    # run monitor playbook with new option setings
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "monitor.yml" );
    my $cmd_opts = { 'monitoring_status' => 'update' };

    # run as daemon in background
    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'monitor', $cmd_opts );

    return $created_process;
}

# change config file for monitoring
# run playbook monitor.yml
sub monitorDisable {
    my $self = shift;

    # get current status of monitoring
    my $monitor = $self->monitorStatus;
    if ( $monitor->is_error ) {
        return Output->new( error => 1, message => "Cannot read config file" );
    }
    my $monitor_status = $monitor->get_data;

    # current monitoring options
    my $mon_file = $monitor_status->[1]->{'monitoring_file'};
    my $mon_flag = $monitor_status->[1]->{'monitoring_status'};
    my $mon_mgmt = $monitor_status->[1]->{'monitoring_server'};

    my $ansData = $self->ansible_conf;
    my $bxData  = $self->bitrix_conf;

    # existen or not file with default value for group bitrix-hosts
    if ( !$mon_file ) {
        return Output->new(
            error   => 1,
            message => "Monitoring does not enable"
        );
    }

    # if
    if ( $mon_flag =~ /^enable$/ ) {

        # run monitor playbook with new option setings
        my $cmd_play = $ansData->{'playbook'};
        my $cmd_conf = catfile( $ansData->{'base'}, "monitor.yml" );
        my $cmd_opts = { 'monitoring_status' => 'disable' };

        # run as daemon in background
        my $dh = bxDaemon->new(
            debug    => $self->debug,
            task_cmd => qq($cmd_play $cmd_conf)
        );
        my $created_process = $dh->startAnsibleProcess( 'monitor', $cmd_opts );

        return $created_process;

    }
    else {
        return Output->new(
            error   => 0,
            message => "Monitoring already disable for pool",
            data    => $monitor_status
        );
    }

}

sub update_pool {
    my ( $self, $host, $type ) = @_;
    if ( not defined $type ) {
        $type = "bx_update";
    }

    my ($host_ident);

    if ( defined $host ) {
        my $get_host_ident = $self->get_inventory_hostname($host);
        return $get_host_ident if ( $get_host_ident->is_error );
        $host_ident = $get_host_ident->data->[1];
    }

    # initilize data
    my $ansData = $self->ansible_conf;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "common.yml" );
    my $cmd_opts = { 'common_manage' => 'version' };
    if ( $type eq "bx_upgrade" ) {
        $cmd_opts->{common_manage} = "update_packages";
    }

    if ( defined $host_ident ) {
        $cmd_opts->{'common_server'} = $host_ident;
    }

    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'update', $cmd_opts );

    return $created_process;
}

sub reboot_server {
    my ( $self, $host ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $get_host_ident = $self->get_inventory_hostname($host);
    return $get_host_ident if ( $get_host_ident->is_error );

    my $host_ident = $get_host_ident->data->[1];

    # initilize data
    my $ansData = $self->ansible_conf;

    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "common.yml" );
    my $cmd_opts = {
        'common_manage' => 'reboot',
        'common_server' => $host_ident
    };

    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'reboot', $cmd_opts );

    return $created_process;
}

sub timezone_in_the_pool {
    my $self   = shift;
    my $tz     = shift;    # timezone, default: Europe/Moscow
    my $tz_php = shift;    # update php settings or not

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    if ( not defined $tz_php ) {
        $tz_php = 1;
    }

    my $update_str = 'update';
    if ( $tz_php == 0 ) { $update_str = 'not_update'; }

    if ( not defined $tz ) {
        $tz = 'Europe/Moscow';
    }

    # initilize data
    my $ansData = $self->ansible_conf;

    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "configure_timezone.yml" );
    my $cmd_opts = {
        'timezone_string'     => $tz,
        'timezone_php_update' => $update_str
    };

    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'configure_tz', $cmd_opts );

    return $created_process;
}

sub password_on_server {
    my ( $self, $host, $user, $password ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    if (   ( not defined $host )
        || ( not defined $user )
        || ( not defined $password ) )
    {
        return Output->new(
            error   => 1,
            message => "$message_p: host, user, password is mandatory",
        );
    }
    my $get_host_ident = $self->get_inventory_hostname($host);
    return $get_host_ident if ( $get_host_ident->is_error );

    my $host_ident = $get_host_ident->data->[1];

    # create file for chpasswd
    my $tmp_dir = "/opt/webdir/keys";
    if ( !-d $tmp_dir ) {
        mkdir $tmp_dir;
        chmod 0700, $tmp_dir;
    }
    my $tmp_file = catfile( $tmp_dir, "source_passwd" );
    open( my $th, '>', $tmp_file )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open $tmp_file: $!",
      );
    print $th "$user:$password";
    close $th;

    # initilize data
    my $ansData = $self->ansible_conf;

    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "common.yml" );
    my $cmd_opts = {
        'common_manage' => 'password',
        'common_server' => $host_ident,
        'common_file'   => $tmp_file,
        'common_user'   => $user
    };

    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'password', $cmd_opts );

    return $created_process;
}

sub deleteSSHFinger {
    my $self   = shift;
    my $old_ip = shift;    # old
    my $new_ip = shift;

    $old_ip = Pool::esc_chars($old_ip);
    $new_ip = Pool::esc_chars($new_ip);

    my $cmd = qq(ssh-keygen -R $old_ip >/dev/null 2>&1);
    system($cmd) == 0
      or return Output->new(
        error   => 1,
        message => "cmd \`ssh-keygen -R $old_ip\` return error: $!",
      );

    # get new rsa key
    my $cmd_gen =
      qq(ssh-keyscan -t rsa $new_ip >> /root/.ssh/known_hosts 2>/dev/null);
    system($cmd_gen) == 0
      or return Output->new(
        error   => 1,
        message => "cmd \`ssh-keyscan -t rsa $new_ip\` return error: $!",
      );

    return Output->new(
        error   => 0,
        message => "known_hosts updated",
    );
}

sub replaceIPAddress {
    my $self      = shift;
    my $old_value = shift;
    my $new_value = shift;
    my $config    = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    $old_value =~ s/\./\\\./g;
    my $tmp_config = $config . ".tmp";
    open( my $tmp, '>', "$tmp_config" )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open $tmp_config: $!",
      );

    open( my $cfg, '<', "$config" )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open $config: $!",
      );
    while (<$cfg>) {
        s/\b$old_value\b/$new_value/;
        print $tmp $_;
    }

    close $cfg;
    close $tmp;
    unlink $config;
    rename $tmp_config, $config;
    chmod 0640, $config;
    return Output->new(
        error   => 0,
        message => "$message_p: $config updated\n"
    );

}

sub update_network {
    my ( $self, $host, $new_ip ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug     = $self->debug;
    my $logOutput = Output->new(
        error   => 0,
        logfile => $self->logfile,
        debug   => $self->debug,
    );
    my $get_host_ident = $self->get_inventory_hostname($host);
    return $get_host_ident if ( $get_host_ident->is_error );
    my $host_ident = $get_host_ident->data->[1];

    if ( not defined $new_ip ) {
        return Output->new(
            error   => 1,
            message => "New IP is mandatory option",
        );
    }

    $logOutput->log_data(
"$message_p: change ipaddress for server=$host_ident from the config files"
    );

    # get current ip address
    my $current_ip    = undef;
    my $get_all_hosts = $self->get_ansible_data();
    if ( $get_all_hosts->is_error ) { return $get_all_hosts }
    my $data_all_hosts = $get_all_hosts->get_data->[1];

    # found host by host_id
    foreach my $ident ( keys %$data_all_hosts ) {
        if ( $ident eq $host_ident ) {
            $current_ip = $data_all_hosts->{$ident}->{'ip'};
        }
    }

    if ( !$current_ip ) {
        return Output->new(
            error   => 1,
            message => "$message_p: not found host host_id=$host_ident"
        );
    }

    $logOutput->log_data(
        "$message_p: ident=$host_ident old_ip=$current_ip new_ip=$new_ip");

   # have to update all group settings and personal host settings and hosts file
   # after that start common task + monitor task
    my $ansible_conf  = $self->ansible_conf;
    my $bitrix_conf   = $self->bitrix_conf;
    my @updated_files = (
        $ansible_conf->{'hosts'},
        catfile( $ansible_conf->{'host_vars'}, $host_ident ),
    );
    foreach my $role ( @{ $bitrix_conf->{'aHostsGroups'} } ) {
        my $fp = catfile( $ansible_conf->{'group_vars'},
            $bitrix_conf->{'aHostsPrefix'} . '-' . $role . '.yml' );
        if ( -f $fp ) {
            push @updated_files, $fp;
        }
    }

    # update files
    #print "$current_ip, $new_ip\n";
    foreach my $cfg (@updated_files) {
        my $update_file = $self->replaceIPAddress( $current_ip, $new_ip, $cfg );
        if ( $update_file->is_error ) { return $update_file; }
    }

    # update ssh keygen
    my $update_known_hosts = $self->deleteSSHFinger( $current_ip, $new_ip );

    # start ansible common play
    my $cmd_play        = $ansible_conf->{'playbook'};
    my $cmd_conf        = catfile( $ansible_conf->{'base'}, "monitor.yml" );
    my $dh              = bxDaemon->new( task_cmd => qq($cmd_play  $cmd_conf) );
    my $created_process = $dh->startProcess( "network_" . $host_ident );
    return $created_process;

    #return Output->new(error=>0, message => "123");
}

# by unique host_id
sub UpdateHostNetwork {
    my ( $self, $host_id, $new_ip ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug     = $self->debug;
    my $logOutput = Output->new(
        error   => 0,
        logfile => $self->logfile,
        debug   => $self->debug,
    );

    if ( not defined $host_id ) {
        return Output->new(
            error   => 1,
            message => "Host identifier is mandatory option",
        );
    }

    if ( not defined $new_ip ) {
        return Output->new(
            error   => 1,
            message => "New IP is mandatory option",
        );
    }

    $logOutput->log_data(
"$message_p: change ipaddress for host_id=$host_id from the config files"
    );

    # get current ip address
    my $current_ip    = undef;
    my $host_ident    = undef;
    my $get_all_hosts = $self->get_ansible_data();
    if ( $get_all_hosts->is_error ) { return $get_all_hosts }
    my $data_all_hosts = $get_all_hosts->get_data->[1];

    # found host by host_id
    foreach my $ident ( keys %$data_all_hosts ) {
        if ( $data_all_hosts->{$ident}->{host_id} eq $host_id ) {
            $current_ip = $data_all_hosts->{$ident}->{'ip'};
            $host_ident = $ident;
        }
    }

    if ( !$current_ip ) {
        return Output->new(
            error   => 1,
            message => "$message_p: not found host host_id=$host_id"
        );
    }

    $logOutput->log_data(
        "$message_p: ident=$host_ident old_ip=$current_ip new_ip=$new_ip");

    return $self->update_network( $host_ident, $new_ip );
}

sub TestHostNetwork {
    my $self = shift;
    my $log  = shift;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    my $debug = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );

    if ( !$log ) {
        return Output->new(
            error   => 1,
            message => "$message_p: path to log file is mandatory",
        );
    }
    if ($debug) {
        $logOutput->log_data("$message_p: parse log=$log");
    }

    my %updates;
    my $updates_count = 0;
    open( my $lh, '<', $log )
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot open $log: $!",
      );
    while (<$lh>) {

# 172.17.0.120 - 1402045968_VzleKSTLPM [06/Jun/2014:13:24:56 +0400 - -] 200 "GET /change?client_ip=172.17.0.120 HTTP/1.1" 0 "-" "Updater/www2" "-"
        chomp;
        next if (/^$/);
        if (/^([\d\.]+)\s+\S+\s+(\S+)\s+\[[^\]]+\]\s+(\d+)\s+"([^"]+)"/) {
            my $remote_address = $1;
            my $host_id        = $2;
            my $code           = $3;
            my $request        = $4;
            my ( $type, $uri, $proto ) = split( /\s+/, $request );
            my $client_ip = "";
            if ( $uri =~ m:^/change\?client_ip=([\d\.]+)$: ) {
                $client_ip = $1;
            }

            #print "$remote_address:$host_id:$code:$client_ip\n";
            if ( $code == 200 && $client_ip !~ /^$/ ) {
                $updates{$host_id} = $remote_address;
                $updates_count++;
            }
        }
    }
    close $lh;

    if ( $updates_count > 0 ) {
        foreach my $host_id ( keys %updates ) {
            my $update_host =
              $self->UpdateHostNetwork( $host_id, $updates{$host_id} );
            if ( $update_host->is_error ) { return $update_host; }
        }
    }
    else {
        return Output->new(
            error   => 0,
            message => "$message_p: Not found request for network update",
        );
    }

    unlink $log;
    my $nginx_cmd = "/sbin/service nginx reload 1>/dev/null 2>/dev/null";
    system($nginx_cmd) == 0
      or return Output->new(
        error   => 1,
        message => "$message_p: Cannot reload nginx service",
      );

    my $update_servers = join( ', ', keys %updates );
    return Output->new(
        error   => 0,
        message => "$message_p: Updated $update_servers",
    );
}

sub beta_version {
    my $self = shift;
    my $type = shift;

    $type = "disable" if ( not defined $type );

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;

    # initilize data
    my $ansData = $self->ansible_conf;

    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "beta_version.yml" );
    my $cmd_opts = { 'beta_version' => $type };

    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'beta_version', $cmd_opts );

    return $created_process;
}

1;