#!/usr/bin/perl
# make_multiple_sandbox
# The MySQL Sandbox
# Copyright (C) 2006-2018 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
#
#
# 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 English qw( -no_match_vars );
use Getopt::Long qw(:config no_ignore_case );
use MySQL::Sandbox qw(get_ranges runs_as_root use_env sbinstr);
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__