The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl 
# make_multiple_sandbox
#    The MySQL Sandbox
#    Copyright (C) 2006-2017 Giuseppe Maxia
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

use strict;
use warnings;
use English qw( -no_match_vars ); 
use Data::Dumper;
use Getopt::Long qw(:config no_ignore_case );
use MySQL::Sandbox qw(get_ranges runs_as_root use_env sbinstr);
use MySQL::Sandbox::Scripts;

my $DEBUG = $MySQL::Sandbox::DEBUG;

runs_as_root();

unless ( -d $ENV{'SANDBOX_HOME'} ) {
    mkdir $ENV{'SANDBOX_HOME'} 
        or die "can't create $ENV{'SANDBOX_HOME'} ($CHILD_ERROR) \n";
}
#my $install_dir;
#$install_dir = $PROGRAM_NAME;
#$install_dir =~ s{/\w+(\.pl)?$}{};
#eval "use lib q($install_dir)";
#eval "use MySandbox";
#eval "use MyScripts";

my $msb = MySQL::Sandbox->new();

my %defaults = (
    circular_base_port => $MySQL::Sandbox::default_base_port{multiple},,
    group_directory => 'multi_msb',
);
$msb->parse_options ( MySQL::Sandbox::Scripts::parse_options_many() );

# my %{$msb->{options}} = map { $_ , $msb->{parse_options}{$_}{'value'}}  keys %{$msb->{parse_options}} ;

GetOptions (
    map { $msb->{parse_options}{$_}{parse}, \$msb->{options}{$_} }        
        grep { $msb->{parse_options}{$_}{parse}}  keys %{$msb->{parse_options}}  
) or $msb->get_help();

$msb->get_help() if $msb->{options}{'help'};

unless ($msb->{options}{server_version}) {
    $msb->{options}{server_version} = shift
        or $msb->get_help('server version required');
}

for my $opt (qw(repl_user repl_password remote_access)) {
    if (defined $msb->{options}{$opt}) {
        $MySQL::Sandbox::default_users{$opt} = $msb->{options}{$opt};
    }
}

if ($msb->{options}{node_options})
{
    $ENV{NODE_OPTIONS} = $msb->{options}{node_options};
}

if ($msb->{options}{one_node_options})
{
    for my $node_opt (@{ $msb->{options}{one_node_options}} )
    {
        if ($node_opt =~ /^(\d+):\s*(\S.+)/)
        {
            $ENV{"NODE${1}_OPTIONS"} = $2;
        }
        else
        {
            get_help("invalid format for --one_node_option ($node_opt)");    
        }
    }
}

my $base_version ;
my $release_no;
my $dashed_version ;
my $replication_port = 0;

my $temp_path = $msb->{options}{server_version};
$temp_path =~ s{.*/}{};
my $prefix = '';

if ( $temp_path =~ /^(\D+)\d+/ ) {
    $prefix = $1;
}
if ( $temp_path =~ /(\d+)\.(\d+)\.(\d+)/ ) {
    $base_version = "$1$2$3";
    $release_no = $3;
    $dashed_version = "${prefix}$1_$2_$3";
    $base_version =~ s/^0+//;
}
else {
    $msb->get_help("No recognizable version pattern in $msb->{options}{server_version}\n");
}

#print Dumper(\%{$msb->{options}});

my $group_directory ;

if ( $msb->{options}{group_directory}) {
    $group_directory = "$msb->{options}{upper_directory}/$msb->{options}{group_directory}";
}
else {
    $group_directory = "$msb->{options}{upper_directory}/$defaults{group_directory}_$dashed_version";
}

if ( -d $group_directory ) {
    if ( -x "$group_directory/clear_all" ) {
        system "$group_directory/clear_all";
    }
    system "rm -rf $group_directory/*";
}
else {
    print "creating group directory $group_directory\n" if $msb->{options}{verbose};
    mkdir $group_directory
            or die "can't create $group_directory\n";
}

if ( -f "$group_directory/needs_initialization" ) {
    system "rm -f $group_directory/needs_initialization" ;
}

if ($msb->{options}{'master_master'}) {
    $msb->{options}{'how_many_nodes'} = 2;
    $msb->{options}{'circular'} = 1;
}

for my $dir ( 1 .. $msb->{options}{how_many_nodes}) {
    if ( -d "$group_directory/node$dir" ) {
        system "rm -rf $group_directory/node$dir" 
            and die "unable to clean $group_directory/node$dir\n";
    }
}

if ( $msb->{options}{sandbox_base_port}) {
    $replication_port = $msb->{options}{sandbox_base_port};
}
else {
    $replication_port = $defaults{circular_base_port} + $base_version + ($release_no * 100);
}

if ( $msb->{options}{check_base_port}) {
    $replication_port = get_ranges({
                min_range   => $replication_port,
                range_size  => $msb->{options}{how_many_nodes},
                max_range   => 64000,
                search_path => $ENV{SANDBOX_HOME},
            }, 1);
}
my $circular_replication_script = "$group_directory/set_circular_replication.sh";

if ($msb->{options}{'circular'}) {
$msb->write_to($circular_replication_script, '>', 
qq(
#!/bin/bash
TMPL1="change master to master_host='127.0.0.1',"
TMPL2="master_user='$MySQL::Sandbox::default_users{'repl_user'}',"
TMPL3="master_password='$MySQL::Sandbox::default_users{'repl_password'}',"
TMPL4="master_port"
TMPL="\$TMPL1 \$TMPL2 \$TMPL3 \$TMPL4"
));
$msb->write_to($circular_replication_script, '>>', "$group_directory/use_all 'reset master' ");
$msb->write_to($circular_replication_script, '>>', "$group_directory/use_all 'stop slave' ");
}

for my $node (1 .. $msb->{options}{how_many_nodes}) {
    my $additional_node_options = $ENV{NODE_OPTIONS} || '';
    if ($msb->{options}{gtid})
    {
        $additional_node_options .= ' --gtid ';
    }
    my $this_node_options = $ENV{"NODE${node}_OPTIONS"} || '';
    my $node_port = $replication_port+ $node;
    if ($msb->{options}{'circular'}) {
        my $master_node_port = $node == 1 
                ? $node_port + $msb->{options}{how_many_nodes} -1
                : $node_port -1;
        $additional_node_options .= '-c log-slave-updates '
            . '-c replicate-same-server-id=0 '
            . '-c auto_increment_increment=' . $msb->{options}{'how_many_nodes'} 
            . ' '
            . '-c auto_increment_offset=' . $node 
            . ' '
            . '-c report-host=SBnode' . $node 
            . ' '
            . "-c report-port=$node_port" ;
    } 
    $additional_node_options .=  q{ };
    my $node_id = 100 + $node;
    print "installing node $node\n";
    my $install_node = '';
    my $install_node_command = qq(
    make_sandbox $msb->{options}{server_version} -- \\
    --datadir_from=script \\
    --upper_directory=$group_directory \\
    --sandbox_directory=node$node \\
    --history_dir=$group_directory \\
    --no_confirm \\
    --no_check_port \\
    --repl_user=$msb->{options}{repl_user} \\
    --repl_password=$msb->{options}{repl_password} \\
    --remote_access=$msb->{options}{remote_access} \\
    --prompt_prefix=node$node \\
    --sandbox_port=$node_port \\
    -c server-id=$node_id \\
    -c relay-log-index=mysql-relay \\
    -c relay-log=mysql-relay \\
    -c log-bin=mysql-bin $additional_node_options $this_node_options
    );
    if ($msb->{options}{interactive} 
        or ($additional_node_options =~ /\binteractive\b/)) {
        system($install_node_command);
    }
    else {
        $install_node = qx($install_node_command);
    }
    print $install_node if $msb->{options}{verbose};
    if ($CHILD_ERROR) {
        print "error installing node $node\n";
        print "$install_node\n($CHILD_ERROR $OS_ERROR)\n";
        exit(1)
    }
    #sleep 1;
    if ($msb->{options}{'circular'}) {
        my $master_node = $node + 1 ;
        if ($master_node >  $msb->{options}{'how_many_nodes'} ) {
            $master_node = 1;
        }
        $msb->write_to($circular_replication_script, '>>',
            sprintf(q(echo "$TMPL=%d" | %s/node%d/use -u root ),
                $node_port,
                $group_directory,
                $master_node
            )
        ); 
    }
}

if ($msb->{options}{'circular'}) {
    $msb->write_to($circular_replication_script, '>>', "$group_directory/use_all 'start slave' ");
    chmod 0755, $circular_replication_script;
}

my $current_dir = $ENV{PWD};

chdir $group_directory;

for my $cmd ( qw(start stop clear send_kill ) ) {
    my $cmd_fname = $cmd . '_all';
    $msb->write_to($cmd_fname, '>', '#!/bin/sh');
    $msb->write_to($cmd_fname, '>>', qq(echo '# executing "$cmd"' on $group_directory));
    if ($cmd =~ /stop|clear|send_kill/ && $msb->{options}{'circular'}) {
        $msb->write_to ($cmd_fname, 
                '>>', 
                qq($group_directory/use_all "stop slave"));
    } 
    for my $node (1 .. $msb->{options}{how_many_nodes} ) {
        $msb->write_to ($cmd_fname, '>>', qq(echo 'executing "$cmd" on node $node'));
        $msb->write_to ($cmd_fname, '>>', qq($group_directory/node$node/$cmd "\$@"));
        if (($cmd eq 'start') and $msb->{options}{'circular'}) {
            $msb->write_to ($cmd_fname, '>>', 
                      "if [ -f $group_directory/node$node/data/master.info ] \n"
                    . "then\n"
                    . "  echo 'stop slave' | $group_directory/n$node -u root\n"
                    . "fi");
        }
    }
    chmod 0755, $cmd_fname;
}

$msb->write_to('restart_all', '>', '#!/bin/sh');
$msb->write_to('restart_all', '>>', qq($group_directory/stop_all));
$msb->write_to('restart_all', '>>', qq($group_directory/start_all "\$@"));
chmod 0755, 'restart_all';

$msb->write_to('use_all', '>', '#!/bin/sh');
my $node_list = join(' ', (1 .. $msb->{options}{how_many_nodes}) );

$msb->write_to('use_all', '>>', 'if [ "$1 " = " " ]');
$msb->write_to('use_all', '>>', 'then');
$msb->write_to('use_all', '>>', '  echo "syntax: $0 command"');
$msb->write_to('use_all', '>>', '  exit 1');
$msb->write_to('use_all', '>>', 'fi');
$msb->write_to('use_all', '>>', '');
$msb->write_to('use_all', '>>', 'for SNUM in ' . $node_list);
$msb->write_to('use_all', '>>', 'do ' );
$msb->write_to('use_all', '>>', '  echo "# server: $SNUM: " ' );
$msb->write_to('use_all', '>>', '  echo "$@" | ' . $group_directory . '/node$SNUM/use $MYCLIENT_OPTIONS ');
$msb->write_to('use_all', '>>', 'done ' );
chmod 0755, 'use_all';

$msb->write_to('status_all', '>', '#!/bin/sh');
my $gdir = $group_directory;
$gdir =~ s{.*/}{};
my $group_type = 'MULTIPLE';
if ($msb->{options}{'circular'}) {
    $group_type = 'CIRC-REPL';
}
$msb->write_to('status_all', '>>', "echo $group_type $gdir" );

$msb->write_to('status_all', '>>', 'for SNUM in ' . $node_list);
$msb->write_to('status_all', '>>', 'do ' );
$msb->write_to('status_all', '>>', '  ' . $group_directory . '/node$SNUM/status');
$msb->write_to('status_all', '>>', '  ' . $group_directory . '/node$SNUM/use -BN -e'
            . qq( "select CONCAT('port: ', \@\@port) AS port" ));
$msb->write_to('status_all', '>>', 'done ' );
chmod 0755, 'status_all';

$msb->write_to('check_slaves', '>', '#!/bin/sh' );
$msb->write_to('check_slaves', '>>', 'for NNUM in ' . $node_list );
$msb->write_to('check_slaves', '>>', 'do' );
$msb->write_to('check_slaves', '>>', '  echo "node # $NNUM"');
$msb->write_to('check_slaves', '>>', '  ' . $group_directory . '/node$NNUM/use -BN -e'
            . qq( "select CONCAT('port: ', \@\@port) AS port" ));
$msb->write_to('check_slaves', '>>', "  $group_directory/node\$NNUM/use -e "
            . qq( 'show master status\\G' | grep "File\\|Position" ));
$msb->write_to('check_slaves', '>>', ' echo "" ' );
$msb->write_to('check_slaves', '>>', "  $group_directory/node\$NNUM/use -e "
            . qq( 'show slave status\\G' | grep "\\(Running:\\|Master_Log_Pos\\|\\<Master_Log_File\\)") );
$msb->write_to('check_slaves', '>>', 'done ' );
chmod 0755, 'check_slaves';




for my $node (1 .. $msb->{options}{how_many_nodes} ) {
    $msb->write_to("n$node", '>', "#!/bin/sh");
    $msb->write_to("n$node", '>', qq($group_directory/node$node/use "\$@"));
    chmod 0755, "n$node";
}

if ($msb->{options}{'circular'}) {

    $msb->write_to(
        "$group_directory/clear_all", '>>', 
                "date > $group_directory/needs_initialization");
    $msb->write_to(qq($group_directory/start_all), '>>', 
                  "if [ -f $group_directory/needs_initialization ] \n"
                . "then\n"
                . "  $circular_replication_script\n"
                . "  rm -f $group_directory/needs_initialization\n"
                . "else\n"
                . "  $group_directory/use_all 'start slave' \n"
                . "fi"); 
   
    #
    # Creates the replication user in all nodes
    #
    ## system qq( MYCLIENT_OPTIONS='-u root' ./use_all 'grant replication slave on *.* )
    ##    . qq( to "$MySQL::Sandbox::default_users{'repl_user'}"\@"127.%" ) . 
    ##    . qq(identified by "$MySQL::Sandbox::default_users{'repl_password'}"')
    ##        and die "can't create slave user";
  
    my $result = system($circular_replication_script);
    if ($result) {
        die ("error starting up circular replication ($!)\n");       
    }
    else {
        print "Circular replication activated\n";
    }
}

my @nodes ;
for my $N (1 .. $msb->{options}{how_many_nodes})
{
    push @nodes, "node$N";
}

for my $json (qw(connection.json default_connection.json))
{
    my $json_text = MySQL::Sandbox::get_json_from_dirs(\@nodes, $json);
    $msb->write_to($json, '>', $json_text);
}

if ($msb->{options}{circular})
{
    $msb->write_to('README', '>', MySQL::Sandbox::Scripts::get_readme_circular());
    $msb->write_to('README', '>>', MySQL::Sandbox::Scripts::get_readme_common_replication());
}
else
{
    $msb->write_to('README', '>', MySQL::Sandbox::Scripts::get_readme_multiple());
}
$msb->write_to('README', '>>', MySQL::Sandbox::Scripts::get_readme_common());


chdir $current_dir;
print "group directory installed in ", use_env($group_directory),"\n";
sbinstr( "group_directory installed in  <" .
        use_env($group_directory) . ">");

__END__