Your IP : 18.222.36.9


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

# manage mysql instance
#
package bxMysql;
use strict;
use warnings;
use Moose;
use Moose::Exporter;
use File::Basename qw( dirname basename );
use File::Spec::Functions;
use Data::Dumper;
use DBI;
use Output;
use Pool;
use bxDaemon;
use bxSites;
use bxInventory qw( get_from_yaml generate_password generate_tmp);

# basic path for site
has 'config', is => 'ro', default => '/etc/ansible/group_vars/bitrix-mysql.yml';
has 'options',
  is      => 'rw',
  lazy    => 1,
  builder => 'parseConfig';    # union cmd options and config options
has 'cmd', is => 'ro';                   # cmd options for mysql group
has 'debug', is => 'ro', default => 0;
has 'logfile', is => 'ro', default => '/opt/webdir/logs/bxMysql.debug';

our $CACHE_DIR = '/opt/webdir/tmp';

## get options from ConfigFile
# if opt found in cmd than use it, else use from config
# /etc/ansible/group_vars/bitrix-mysql:
sub parseConfig {
    my $self = shift;

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

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

    my $mysql_options = {
        master_server    => undef,
        mysql_last_id    => 1,
        mysql_host       => 'localhost',
        mysql_port       => 3306,
        mysql_socket     => '/var/lib/mysqld/mysqld.sock',
        cluster_login    => 'bx_clusteruser',
        cluster_password => undef,
        replica_login    => 'bx_repluser',
        replica_password => undef,
    };

    # parse ansible inventory file
    my $rtn = get_from_yaml( $self->config );
    if ( $rtn->is_error ) {
        die $rtn->message;
    }
    my $opts = $rtn->data->[1];
    foreach my $k ( keys %$mysql_options ) {
        if ( ( defined $opts->{$k} ) && ( $opts->{$k} !~ /^\s*$/ ) ) {
            $mysql_options->{$k} = $opts->{$k};
        }
    }

    # parse command line opts
    $logOutput->log_data("$message_p: parse cmd options");
    foreach my $key ( keys %$mysql_options ) {
        if ( defined $cmdOpt->{$key} && not defined $mysql_options->{$key} ) {
            $mysql_options->{$key} = $cmdOpt->{$key};
            $mysql_options->{$key} =~ s/^['"]//;
            $mysql_options->{$key} =~ s/['"]$//;
            $logOutput->log_data( "$message_p:  replace opt by cmd line $key="
                  . $cmdOpt->{$key} );
        }
    }

    # return
    $logOutput->log_data(
        "$message_p: finished processing config=" . $self->config );
    return $mysql_options;
}

## get server options
sub serverOptions {
    my ( $self, $server_name ) = @_;

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

    # return value
    my $server_options = {
        type => '',
        id   => '',
        ip   => '',
    };

    # get all host info
    my $po = Pool->new();
    my $get_host_ident = $po->get_inventory_hostname($server_name);
    return $get_host_ident if ($get_host_ident->is_error);
    my $host_ident = $get_host_ident->data->[1];

    #print "hostname: $server_name\n";
    my $host_info = $po->get_ansible_data($host_ident);
    if ( $host_info->is_error ) { return $host_info }
    my $host_data   = $host_info->get_data;
    my $server_hash = $host_data->[1];

    # output only mysql option
    if ( grep /^mysql$/, keys %{ $server_hash->{$host_ident}->{'roles'} } ) {
        $server_options->{type} =
          $server_hash->{$host_ident}->{'roles'}->{'mysql'}->{'type'};
        $server_options->{id} =
          $server_hash->{$host_ident}->{'roles'}->{'mysql'}->{'id'};
        $server_options->{'mysql'} = 1;
    }else{

        $server_options->{'mysql'} = 0;
    }

    $server_options->{'ip'} = $server_hash->{$host_ident}->{'ip'};
    $server_options->{bx_netaddr} = $server_hash->{$host_ident}->{host_vars}->{bx_netaddr};

    return Output->new(
        error => 0,
        data  => [ $message_t, { $host_ident => $server_options } ]
    );
}

## get current mysql servers list
sub serverList {
    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 );

    my $po           = Pool->new();
    my $get_mysql_servers = $po->get_inventory_hostname_at_group('mysql');
    return $get_mysql_servers if ($get_mysql_servers->is_error);
    my $mysql_servers = $get_mysql_servers->data->[1];
    foreach my $srv (keys %$mysql_servers){
        $mysql_servers->{$srv}->{id} = $mysql_servers->{$srv}->{roles}->{mysql}->{id};
        $mysql_servers->{$srv}->{type} = $mysql_servers->{$srv}->{roles}->{mysql}->{type};
        $mysql_servers->{$srv}->{bx_netaddr} = $mysql_servers->{$srv}->{host_vars}->{bx_netaddr};
        delete $mysql_servers->{$srv}->{roles};
        delete $mysql_servers->{$srv}->{host_vars};
    }

    if ($debug) {
        $logOutput->log_data(
            "$message_p: finished processing for mysql servers");
    }
    return Output->new( 
        error => 0, 
        data => [ $message_t, $mysql_servers ] );
}

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


    my $mysql_options = $self->options;

    # test mysql options - generate passwords
    foreach my $key ( keys %$mysql_options ) {
        if ( $key =~ /^(cluster|replica)_password$/ ) {
            # generate passwords
            if ( not defined $mysql_options->{$key} ) {
                my $pass = generate_password();
                my $tmp = generate_tmp($key, $CACHE_DIR);
                open (my $h, ">", $tmp)
                    or die "Cannot open temporary file=$tmp: $!";
                print $h $pass;
                close $h;
                $mysql_options->{$key."_file"} = $tmp;
            }
            # old version push password like plain text, new one like a file
            # make differences
            else {
                if ( -f $mysql_options->{$key} ){
                    $mysql_options->{$key.'_file'} = $mysql_options->{$key};
                }else{
                    my $tmp = generate_tmp($key, $CACHE_DIR);
                    open (my $h, ">", $tmp)
                        or die "Cannot open temporary file=$tmp: $!";
                    print $h $mysql_options->{$key};
                    close $h;
                    $mysql_options->{$key."_file"} = $tmp;
                }
            }
            delete $mysql_options->{$key};
        }
        elsif ( $key =~ /^(mysql_host|mysql_port|mysql_socket)$/ ){
            delete  $mysql_options->{$key};
        }
    }

    return $mysql_options;
}

## create mysql slave
# input:
# hostname in pool - host ip address
# *password options mandatory, but can be defined inf config file
sub slave {
    my ( $self, $server_name ) = @_;

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

    # test host options
    my $get_host_options = $self->serverOptions($server_name);
    return $get_host_options if ($get_host_options->is_error);
    my $host_options = $get_host_options->data->[1];
    my ($host_ident) = keys %$host_options;
    if ($host_options->{$host_ident}->{mysql}){
        return Output->new(
            error => 1,
            message => "$message_p: $server_name already configured as "
            . $host_options->{$host_ident}->{type}
            . "mysql server",
        )
    }

    # test sites; scale and cluster modules + number kernels sites
    my $bxSites           = bxSites->new();
    my $testClusterConfig = $bxSites->testClusterConfig();
    my $test_data         = $testClusterConfig->get_data->[1];
    if (   ( $test_data->{'test_kernels'} > 1 )
        || ( $test_data->{'test_without_scale'} > 0 )
        || ( $test_data->{'test_without_cluster'} > 0 ) )
    {
        return Output->new(
            error => 1,
            message =>
"$message_p: Found conditions when mysql-cluster configuration is disabled",
        );
    }

    # test if password options exists
    my $mysql_options = $self->mysql_cluster_options;
    $mysql_options->{'group'} = 'mysql';

    # create ansible task options
    my $po       = Pool->new();
    my $ansData  = $po->ansible_conf;
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );
    $mysql_options->{mysql_manage} = "add";
    $mysql_options->{slave_server} = $host_ident;
    
    # run as daemon in background
    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'mysql', $mysql_options );

    return $created_process;
}

sub remove {
    my ( $self, $server_name ) = @_;

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

    # test host options
    my $get_host_options = $self->serverOptions($server_name);
    return $get_host_options if ($get_host_options->is_error);
    my $host_options = $get_host_options->data->[1];
    my ($host_ident) = keys %$host_options;
    if ($host_options->{$host_ident}->{mysql} && 
        $host_options->{$host_ident}->{type} eq 'master'){
        return Output->new(
            error => 1,
            message => "$message_p: $server_name is "
            . $host_options->{$host_ident}->{type}
            . "mysql server; Cannot remove it.",
        )
    }elsif (!$host_options->{$host_ident}->{mysql}){
        return Output->new(
            error => 1,
            message => "$message_p: $server_name isn't mysql server ",
        )
    }

    # create ansible task options
    my $po       = Pool->new();
    my $ansData  = $po->ansible_conf;
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );
    my $mysql_options->{mysql_manage} = "remove";
    $mysql_options->{slave_server} = $host_ident;
    
    # run as daemon in background
    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'mysql', $mysql_options );

    return $created_process;
}

sub master {
    my ( $self, $server_name ) = @_;

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

    # test host options
    my $get_host_options = $self->serverOptions($server_name);
    return $get_host_options if ($get_host_options->is_error);
    my $host_options = $get_host_options->data->[1];
    my ($host_ident) = keys %$host_options;
    if ($host_options->{$host_ident}->{mysql} && 
        $host_options->{$host_ident}->{type} eq 'master'){
        return Output->new(
            error => 1,
            message => "$message_p: $server_name is "
            . $host_options->{$host_ident}->{type}
            . "mysql server; Nothing to do.",
        )
    }elsif (!$host_options->{$host_ident}->{mysql}){
        return Output->new(
            error => 1,
            message => "$message_p: $server_name isn't mysql server ",
        )
    }

    # create ansible task options
    my $po       = Pool->new();
    my $ansData  = $po->ansible_conf;
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );
    my $mysql_options->{mysql_manage} = "master";
    $mysql_options->{slave_server} = $host_ident;
    $mysql_options->{serverid} = $host_options->{$host_ident}->{id};
    
    # run as daemon in background
    my $dh = bxDaemon->new(
        debug    => $self->debug,
        task_cmd => qq($cmd_play $cmd_conf)
    );
    my $created_process = $dh->startAnsibleProcess( 'mysql', $mysql_options );

    return $created_process;
}




# update mysql settings on servers
sub update {
    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: start update mysqls in the pool");

    # test if password options exists
    my $mysql_options = $self->mysql_cluster_options;
    $mysql_options->{'group'} = 'mysql';
    $mysql_options->{mysql_manage} = 'update';

    # create ansible task options
    my $po      = Pool->new();
    my $ansData = $po->ansible_conf;

    # update group configuration by value that user defined
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );

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

sub password {
    my ( $self, $server_name, $mysql_manage ) = @_;

    my $message_p = ( caller(0) )[3];
    my $message_t = __PACKAGE__;
    my $debug     = $self->debug;
    my $logOutput = Output->new( error => 0, logfile => $self->logfile );
    my $cmd       = $self->cmd;
    
    # get all host info
    my $po = Pool->new();
    my $get_host_ident = $po->get_inventory_hostname($server_name);
    return $get_host_ident if ($get_host_ident->is_error);
    my $host_ident = $get_host_ident->data->[1];
  
    ( $cmd->{password_file} )
      or return Output->new(
        error    => 1,
        messages => "$message_p: password_file is mandatory option"
      );

    $logOutput->log_data("$message_p: $mysql_manage on server=$server_name");

    # test if server name doesn't exist in configuration
    my $get_server_opt = serverOptions( $self, $host_ident );
    if ( $get_server_opt->is_error ) { return $get_server_opt }
    if ($debug) { $logOutput->log_data("$message_p: get server options"); }

# create ansible task options
# ansible-playbook  /etc/ansible/mysql.yml -vvv -e "replica_password=XXXXXXXX cluster_password=XXXXXXXXX "
    my $ansData  = $po->ansible_conf;
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );
    my $cmd_opts = {
        'mysql_manage'  => $mysql_manage,
        'slave_server'  => $host_ident,
        'password_file' => $cmd->{password_file},
    };

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

    return $created_process;
}

sub manage {
    my ( $self, $server_name, $mysql_manage ) = @_;

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

    my $po = Pool->new();
    my $get_host_ident = $po->get_inventory_hostname($server_name);
    return $get_host_ident if ($get_host_ident->is_error);
    my $host_ident = $get_host_ident->data->[1];
 
    $logOutput->log_data("$message_p: $mysql_manage on server=$server_name");

    # test if server name doesn't exist in configuration
    my $get_server_opt = serverOptions( $self, $host_ident );
    if ( $get_server_opt->is_error ) { return $get_server_opt }
    if ($debug) { $logOutput->log_data("$message_p: get server options"); }

# create ansible task options
# ansible-playbook  /etc/ansible/mysql.yml -vvv -e "replica_password=XXXXXXXX cluster_password=XXXXXXXXX "
    my $ansData  = $po->ansible_conf;
    my $cmd_play = $ansData->{'playbook'};
    my $cmd_conf = catfile( $ansData->{'base'}, "mysql.yml" );
    my $cmd_opts = {
        'mysql_manage' => $mysql_manage,
        'slave_server' => $host_ident,
    };

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

    return $created_process;
}

1;